From 7019263f1d87d884048e1fdbfbd157801bb3c146 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 10 Nov 2025 14:31:02 +0100 Subject: [PATCH 01/10] feat(node): Add `maxCacheKeyLength` to Redis integration (remove truncation) (#18045) This removes the automatic truncation of the span description. Now, all cache keys are set in the description. part of https://github.com/getsentry/sentry-javascript/issues/17389 --- .../node/src/integrations/tracing/redis.ts | 29 ++++++- .../test/integrations/tracing/redis.test.ts | 81 ++++++++++++++++++- 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 8376c99c1998..f8be12352ae0 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -24,14 +24,34 @@ import { } from '../../utils/redisCache'; interface RedisOptions { + /** + * Define cache prefixes for cache keys that should be captured as a cache span. + * + * Setting this to, for example, `['user:']` will capture cache keys that start with `user:`. + */ cachePrefixes?: string[]; + /** + * Maximum length of the cache key added to the span description. If the key exceeds this length, it will be truncated. + * + * Passing `0` will use the full cache key without truncation. + * + * By default, the full cache key is used. + */ + maxCacheKeyLength?: number; } const INTEGRATION_NAME = 'Redis'; -let _redisOptions: RedisOptions = {}; +/* Only exported for testing purposes */ +export let _redisOptions: RedisOptions = {}; -const cacheResponseHook: RedisResponseCustomAttributeFunction = (span: Span, redisCommand, cmdArgs, response) => { +/* Only exported for testing purposes */ +export const cacheResponseHook: RedisResponseCustomAttributeFunction = ( + span: Span, + redisCommand, + cmdArgs, + response, +) => { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.redis'); const safeKey = getCacheKeySafely(redisCommand, cmdArgs); @@ -70,9 +90,12 @@ const cacheResponseHook: RedisResponseCustomAttributeFunction = (span: Span, red [SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey, }); + // todo: change to string[] once EAP supports it const spanDescription = safeKey.join(', '); - span.updateName(truncate(spanDescription, 1024)); + span.updateName( + _redisOptions.maxCacheKeyLength ? truncate(spanDescription, _redisOptions.maxCacheKeyLength) : spanDescription, + ); }; const instrumentIORedis = generateInstrumentOnce(`${INTEGRATION_NAME}.IORedis`, () => { diff --git a/packages/node/test/integrations/tracing/redis.test.ts b/packages/node/test/integrations/tracing/redis.test.ts index 38a5b80eb759..ae5b879c0b8e 100644 --- a/packages/node/test/integrations/tracing/redis.test.ts +++ b/packages/node/test/integrations/tracing/redis.test.ts @@ -1,4 +1,5 @@ -import { describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { _redisOptions, cacheResponseHook } from '../../../src/integrations/tracing/redis'; import { calculateCacheItemSize, GET_COMMANDS, @@ -8,6 +9,82 @@ import { } from '../../../src/utils/redisCache'; describe('Redis', () => { + describe('cacheResponseHook', () => { + let mockSpan: any; + let originalRedisOptions: any; + + beforeEach(() => { + mockSpan = { + setAttribute: vi.fn(), + setAttributes: vi.fn(), + updateName: vi.fn(), + spanContext: () => ({ spanId: 'test-span-id', traceId: 'test-trace-id' }), + }; + + originalRedisOptions = { ..._redisOptions }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + // Reset redis options by clearing all properties first, then restoring original ones + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + Object.keys(_redisOptions).forEach(key => delete (_redisOptions as any)[key]); + Object.assign(_redisOptions, originalRedisOptions); + }); + + describe('early returns', () => { + it.each([ + { desc: 'no args', cmd: 'get', args: [], response: 'test' }, + { desc: 'unsupported command', cmd: 'exists', args: ['key'], response: 'test' }, + { desc: 'no cache prefixes', cmd: 'get', args: ['key'], response: 'test', options: {} }, + { desc: 'non-matching prefix', cmd: 'get', args: ['key'], response: 'test', options: { cachePrefixes: ['c'] } }, + ])('should always set sentry.origin but return early when $desc', ({ cmd, args, response, options = {} }) => { + Object.assign(_redisOptions, options); + + cacheResponseHook(mockSpan, cmd, args, response); + + expect(mockSpan.setAttribute).toHaveBeenCalledWith('sentry.origin', 'auto.db.otel.redis'); + expect(mockSpan.setAttributes).not.toHaveBeenCalled(); + expect(mockSpan.updateName).not.toHaveBeenCalled(); + }); + }); + + describe('span name truncation', () => { + beforeEach(() => { + Object.assign(_redisOptions, { cachePrefixes: ['cache:'] }); + }); + + it('should not truncate span name when maxCacheKeyLength is not set', () => { + cacheResponseHook( + mockSpan, + 'mget', + ['cache:very-long-key-name', 'cache:very-long-key-name-2', 'cache:very-long-key-name-3'], + 'value', + ); + + expect(mockSpan.updateName).toHaveBeenCalledWith( + 'cache:very-long-key-name, cache:very-long-key-name-2, cache:very-long-key-name-3', + ); + }); + + it('should truncate span name when maxCacheKeyLength is set', () => { + Object.assign(_redisOptions, { maxCacheKeyLength: 10 }); + + cacheResponseHook(mockSpan, 'get', ['cache:very-long-key-name'], 'value'); + + expect(mockSpan.updateName).toHaveBeenCalledWith('cache:very...'); + }); + + it('should truncate multiple keys joined with commas', () => { + Object.assign(_redisOptions, { maxCacheKeyLength: 20 }); + + cacheResponseHook(mockSpan, 'mget', ['cache:key1', 'cache:key2', 'cache:key3'], ['val1', 'val2', 'val3']); + + expect(mockSpan.updateName).toHaveBeenCalledWith('cache:key1, cache:ke...'); + }); + }); + }); + describe('getCacheKeySafely (single arg)', () => { it('should return an empty string if there are no command arguments', () => { const result = getCacheKeySafely('get', []); @@ -26,7 +103,7 @@ describe('Redis', () => { expect(result).toStrictEqual(['key1']); }); - it('should return only the key for multiple arguments', () => { + it('should return only the first key for commands that only accept a singe key (get)', () => { const cmdArgs = ['key1', 'the-value']; const result = getCacheKeySafely('get', cmdArgs); expect(result).toStrictEqual(['key1']); From 8b332543b849368338f1058480fe15005c6eb884 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 10 Nov 2025 14:07:13 +0000 Subject: [PATCH 02/10] feat(browser): Include Spotlight in development bundles (#18078) This PR: - Splits the browser build output into separate development and production builds - References these two builds in the package.json exports - Adds a Rollup plugin to production builds which can strip out code wrapped with development-only magic comments - Adds the spotlight integration to the default integrations in development mode only - Also adds this plugin for browser bundles so spotlight doesn't get included in CDN bundles --- .size-limit.js | 22 +- .../utils/generatePlugin.ts | 12 +- dev-packages/bundler-tests/.eslintrc.js | 6 + .../bundler-tests/fixtures/basic/index.html | 6 + .../bundler-tests/fixtures/basic/index.js | 5 + dev-packages/bundler-tests/package.json | 26 ++ .../bundler-tests/tests/bundling.test.ts | 144 ++++++++++ dev-packages/bundler-tests/vitest.config.mjs | 9 + dev-packages/bundler-tests/webpack.config.js | 10 + dev-packages/rollup-utils/bundleHelpers.mjs | 7 +- dev-packages/rollup-utils/npmHelpers.mjs | 44 ++- .../rollup-utils/plugins/npmPlugins.mjs | 18 ++ dev-packages/rollup-utils/utils.mjs | 11 +- package.json | 3 +- packages/browser/package.json | 23 +- packages/browser/rollup.npm.config.mjs | 1 + packages/browser/src/client.ts | 12 + packages/browser/src/sdk.ts | 17 +- scripts/ci-unit-tests.ts | 1 + yarn.lock | 258 +++++++++--------- 20 files changed, 469 insertions(+), 166 deletions(-) create mode 100644 dev-packages/bundler-tests/.eslintrc.js create mode 100644 dev-packages/bundler-tests/fixtures/basic/index.html create mode 100644 dev-packages/bundler-tests/fixtures/basic/index.js create mode 100644 dev-packages/bundler-tests/package.json create mode 100644 dev-packages/bundler-tests/tests/bundling.test.ts create mode 100644 dev-packages/bundler-tests/vitest.config.mjs create mode 100644 dev-packages/bundler-tests/webpack.config.js diff --git a/.size-limit.js b/.size-limit.js index b4819fc83126..2d07afde52ab 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -5,14 +5,14 @@ module.exports = [ // Browser SDK (ESM) { name: '@sentry/browser', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init'), gzip: true, limit: '25 KB', }, { name: '@sentry/browser - with treeshaking flags', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init'), gzip: true, limit: '24.1 KB', @@ -35,28 +35,28 @@ module.exports = [ }, { name: '@sentry/browser (incl. Tracing)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, limit: '41.3 KB', }, { name: '@sentry/browser (incl. Tracing, Profiling)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'browserProfilingIntegration'), gzip: true, limit: '48 KB', }, { name: '@sentry/browser (incl. Tracing, Replay)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration'), gzip: true, limit: '80 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration'), gzip: true, limit: '75 KB', @@ -79,35 +79,35 @@ module.exports = [ }, { name: '@sentry/browser (incl. Tracing, Replay with Canvas)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, limit: '85 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'), gzip: true, limit: '97 KB', }, { name: '@sentry/browser (incl. Feedback)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'feedbackIntegration'), gzip: true, limit: '42 KB', }, { name: '@sentry/browser (incl. sendFeedback)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'sendFeedback'), gzip: true, limit: '30 KB', }, { name: '@sentry/browser (incl. FeedbackAsync)', - path: 'packages/browser/build/npm/esm/index.js', + path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'feedbackAsyncIntegration'), gzip: true, limit: '35 KB', diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index 0a90b5e2be23..6e3ef99aa7ea 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -46,8 +46,8 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record = { const BUNDLE_PATHS: Record> = { browser: { - cjs: 'build/npm/cjs/index.js', - esm: 'build/npm/esm/index.js', + cjs: 'build/npm/cjs/prod/index.js', + esm: 'build/npm/esm/prod/index.js', bundle: 'build/bundles/bundle.js', bundle_min: 'build/bundles/bundle.min.js', bundle_replay: 'build/bundles/bundle.replay.js', @@ -67,8 +67,8 @@ const BUNDLE_PATHS: Record> = { loader_tracing_replay: 'build/bundles/bundle.tracing.replay.debug.min.js', }, integrations: { - cjs: 'build/npm/cjs/index.js', - esm: 'build/npm/esm/index.js', + cjs: 'build/npm/cjs/prod/index.js', + esm: 'build/npm/esm/prod/index.js', bundle: 'build/bundles/[INTEGRATION_NAME].js', bundle_min: 'build/bundles/[INTEGRATION_NAME].min.js', }, @@ -77,8 +77,8 @@ const BUNDLE_PATHS: Record> = { bundle_min: 'build/bundles/[INTEGRATION_NAME].min.js', }, wasm: { - cjs: 'build/npm/cjs/index.js', - esm: 'build/npm/esm/index.js', + cjs: 'build/npm/cjs/prod/index.js', + esm: 'build/npm/esm/prod/index.js', bundle: 'build/bundles/wasm.js', bundle_min: 'build/bundles/wasm.min.js', }, diff --git a/dev-packages/bundler-tests/.eslintrc.js b/dev-packages/bundler-tests/.eslintrc.js new file mode 100644 index 000000000000..a65cf78e0b57 --- /dev/null +++ b/dev-packages/bundler-tests/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: ['../../.eslintrc.js'], + parserOptions: { + sourceType: 'module', + }, +}; diff --git a/dev-packages/bundler-tests/fixtures/basic/index.html b/dev-packages/bundler-tests/fixtures/basic/index.html new file mode 100644 index 000000000000..40e5dbc0642e --- /dev/null +++ b/dev-packages/bundler-tests/fixtures/basic/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/dev-packages/bundler-tests/fixtures/basic/index.js b/dev-packages/bundler-tests/fixtures/basic/index.js new file mode 100644 index 000000000000..f3d47c97f7a2 --- /dev/null +++ b/dev-packages/bundler-tests/fixtures/basic/index.js @@ -0,0 +1,5 @@ +import { init } from '@sentry/browser'; + +init({ + dsn: 'https://00000000000000000000000000000000@o000000.ingest.sentry.io/0000000', +}); diff --git a/dev-packages/bundler-tests/package.json b/dev-packages/bundler-tests/package.json new file mode 100644 index 000000000000..7cc3dffe89eb --- /dev/null +++ b/dev-packages/bundler-tests/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sentry-internal/bundler-tests", + "version": "10.23.0", + "description": "Bundler tests for Sentry Browser SDK", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bundler-tests", + "author": "Sentry", + "license": "MIT", + "private": true, + "main": "./index.mjs", + "scripts": { + "test": "vitest run" + }, + "dependencies": { + "@sentry/browser": "10.23.0", + "webpack": "^5.0.0", + "rollup": "^4.0.0", + "vite": "^5.0.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "vitest": "^3.2.4" + }, + "volta": { + "extends": "../../package.json" + }, + "type": "module" +} diff --git a/dev-packages/bundler-tests/tests/bundling.test.ts b/dev-packages/bundler-tests/tests/bundling.test.ts new file mode 100644 index 000000000000..1c3f42fc40f4 --- /dev/null +++ b/dev-packages/bundler-tests/tests/bundling.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, beforeAll, test } from 'vitest'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +import webpack from 'webpack'; +import { rollup } from 'rollup'; +import { build as viteBuild } from 'vite'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Helper functions +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function distDir(name: string): string { + const dir = path.join(__dirname, '..', 'dist', name); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + return dir; +} + +function rimraf(dir: string): void { + if (fs.existsSync(dir)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +} + +function readAllJs(outDir: string): string { + let contents = ''; + const stack = [outDir]; + while (stack.length) { + const current = stack.pop()!; + for (const entry of fs.readdirSync(current)) { + const full = path.join(current, entry); + const stat = fs.statSync(full); + if (stat.isDirectory()) { + stack.push(full); + } else if (entry.endsWith('.js') || entry.endsWith('.mjs')) { + contents += fs.readFileSync(full, 'utf8'); + } + } + } + return contents; +} + +function fixtureEntry(name: string): string { + return path.resolve(__dirname, '..', 'fixtures', name, 'index.js'); +} + +function rootDir(): string { + return path.join(__dirname, '../../..'); +} + +const SPOTLIGHT_URL = 'localhost:8969'; + +type BundleMode = 'development' | 'production'; + +function bundleWithWebpack(mode: BundleMode): Promise { + return new Promise((resolve, reject) => { + const outDir = distDir(`webpack-${mode}`); + rimraf(outDir); + const compiler = webpack({ + mode, + entry: fixtureEntry('basic'), + output: { path: outDir, filename: 'bundle.js' }, + }); + compiler?.run((err: Error | null | undefined, stats: webpack.Stats | undefined) => { + try { + if (err) throw err; + if (stats?.hasErrors()) { + throw new Error(stats.toString('errors-only')); + } + resolve(readAllJs(outDir)); + } catch (e) { + reject(e); + } finally { + compiler.close(() => {}); + } + }); + }); +} + +async function bundleWithRollup(mode: BundleMode): Promise { + const outDir = distDir(`rollup-${mode}`); + rimraf(outDir); + + const bundle = await rollup({ + input: fixtureEntry('basic'), + plugins: [ + nodeResolve({ + // There should really be a default where these get specified automatically + exportConditions: [mode === 'production' ? 'production' : 'development'], + }), + ], + }); + await bundle.write({ dir: outDir, format: 'esm' }); + await bundle.close(); + return readAllJs(outDir); +} + +async function bundleWithVite(mode: BundleMode): Promise { + const outDir = distDir(`vite-${mode}`); + rimraf(outDir); + + // In Vitest, NODE_ENV is always 'test', so we need to override it here + const prev = process.env.NODE_ENV; + process.env.NODE_ENV = mode; + + await viteBuild({ + mode, + root: path.dirname(fixtureEntry('basic')), + build: { outDir, minify: mode === 'production' }, + }); + + process.env.NODE_ENV = prev; + + return readAllJs(outDir); +} + +describe('spotlight', () => { + beforeAll(() => { + const distRoot = path.join(rootDir(), 'dist'); + rimraf(distRoot); + }); + + const cases: [string, (mode: BundleMode) => Promise][] = [ + ['webpack', bundleWithWebpack], + ['rollup', bundleWithRollup], + ['vite', bundleWithVite], + ]; + + for (const [name, bundler] of cases) { + test(`${name} development bundle contains spotlight`, async () => { + const code = await bundler('development'); + expect(code).includes(SPOTLIGHT_URL); + }); + + test(`${name} production bundle does not contain spotlight`, async () => { + const code = await bundler('production'); + expect(code).not.includes(SPOTLIGHT_URL); + }); + } +}); diff --git a/dev-packages/bundler-tests/vitest.config.mjs b/dev-packages/bundler-tests/vitest.config.mjs new file mode 100644 index 000000000000..8baf9e1fbf68 --- /dev/null +++ b/dev-packages/bundler-tests/vitest.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.test.*s'], + timeout: 10000, + hookTimeout: 10000, + }, +}); diff --git a/dev-packages/bundler-tests/webpack.config.js b/dev-packages/bundler-tests/webpack.config.js new file mode 100644 index 000000000000..2c3c73b459a4 --- /dev/null +++ b/dev-packages/bundler-tests/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = (env, argv) => ({ + mode: argv.mode || 'development', + entry: path.resolve(__dirname, 'fixtures/basic/index.js'), + output: { + path: path.resolve(__dirname, 'dist/webpack-' + (argv.mode || 'development')), + filename: 'bundle.js', + }, +}); diff --git a/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs index b353eebaa214..8dd2ebd21999 100644 --- a/dev-packages/rollup-utils/bundleHelpers.mjs +++ b/dev-packages/rollup-utils/bundleHelpers.mjs @@ -11,7 +11,6 @@ import { makeCleanupPlugin, makeCommonJSPlugin, makeIsDebugBuildPlugin, - makeJsonPlugin, makeLicensePlugin, makeNodeResolvePlugin, makeRrwebBuildPlugin, @@ -20,6 +19,7 @@ import { makeTerserPlugin, } from './plugins/index.mjs'; import { mergePlugins } from './utils.mjs'; +import { makeProductionReplacePlugin } from './plugins/npmPlugins.mjs'; const BUNDLE_VARIANTS = ['.js', '.min.js', '.debug.min.js']; @@ -35,14 +35,13 @@ export function makeBaseBundleConfig(options) { excludeIframe: false, excludeShadowDom: false, }); + const productionReplacePlugin = makeProductionReplacePlugin(); // The `commonjs` plugin is the `esModuleInterop` of the bundling world. When used with `transformMixedEsModules`, it // will include all dependencies, imported or required, in the final bundle. (Without it, CJS modules aren't included // at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.) const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true }); - const jsonPlugin = makeJsonPlugin(); - // used by `@sentry/browser` const standAloneBundleConfig = { output: { @@ -119,7 +118,7 @@ export function makeBaseBundleConfig(options) { strict: false, esModule: false, }, - plugins: [sucrasePlugin, nodeResolvePlugin, cleanupPlugin], + plugins: [productionReplacePlugin, sucrasePlugin, nodeResolvePlugin, cleanupPlugin], treeshake: 'smallest', }; diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index cff113d622d6..d5f7428b992d 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -16,6 +16,7 @@ import { makeCleanupPlugin, makeDebugBuildStatementReplacePlugin, makeNodeResolvePlugin, + makeProductionReplacePlugin, makeRrwebBuildPlugin, makeSucrasePlugin, } from './plugins/index.mjs'; @@ -114,22 +115,47 @@ export function makeBaseNPMConfig(options = {}) { } export function makeNPMConfigVariants(baseConfig, options = {}) { - const { emitEsm = true, emitCjs = true } = options; + const { emitEsm = true, emitCjs = true, splitDevProd = false } = options; const variantSpecificConfigs = []; if (emitCjs) { - variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } }); + if (splitDevProd) { + variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs/dev') } }); + variantSpecificConfigs.push({ + output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs/prod') }, + plugins: [makeProductionReplacePlugin()], + }); + } else { + variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } }); + } } if (emitEsm) { - variantSpecificConfigs.push({ - output: { - format: 'esm', - dir: path.join(baseConfig.output.dir, 'esm'), - plugins: [makePackageNodeEsm()], - }, - }); + if (splitDevProd) { + variantSpecificConfigs.push({ + output: { + format: 'esm', + dir: path.join(baseConfig.output.dir, 'esm/dev'), + plugins: [makePackageNodeEsm()], + }, + }); + variantSpecificConfigs.push({ + output: { + format: 'esm', + dir: path.join(baseConfig.output.dir, 'esm/prod'), + plugins: [makeProductionReplacePlugin(), makePackageNodeEsm()], + }, + }); + } else { + variantSpecificConfigs.push({ + output: { + format: 'esm', + dir: path.join(baseConfig.output.dir, 'esm'), + plugins: [makePackageNodeEsm()], + }, + }); + } } return variantSpecificConfigs.map(variant => deepMerge(baseConfig, variant)); diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 8c7e0ff10a80..7f08873f1c80 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -125,6 +125,24 @@ export function makeDebugBuildStatementReplacePlugin() { }); } +export function makeProductionReplacePlugin() { + const pattern = /\/\* rollup-include-development-only \*\/[\s\S]*?\/\* rollup-include-development-only-end \*\/\s*/g; + + function stripDevBlocks(code) { + if (!code) return null; + if (!code.includes('rollup-include-development-only')) return null; + const replaced = code.replace(pattern, ''); + return { code: replaced, map: null }; + } + + return { + name: 'remove-dev-mode-blocks', + renderChunk(code) { + return stripDevBlocks(code); + }, + }; +} + /** * Creates a plugin to replace build flags of rrweb with either a constant (if passed true/false) or with a safe statement that: * a) evaluates to `true` diff --git a/dev-packages/rollup-utils/utils.mjs b/dev-packages/rollup-utils/utils.mjs index 94b8c4483c23..b687ff9993c4 100644 --- a/dev-packages/rollup-utils/utils.mjs +++ b/dev-packages/rollup-utils/utils.mjs @@ -21,7 +21,16 @@ export function mergePlugins(pluginsA, pluginsB) { // here. // Additionally, the excludeReplay plugin must run before TS/Sucrase so that we can eliminate the replay code // before anything is type-checked (TS-only) and transpiled. - const order = ['excludeReplay', 'typescript', 'sucrase', '...', 'terser', 'license', 'output-base64-worker-script']; + const order = [ + 'remove-dev-mode-blocks', + 'excludeReplay', + 'typescript', + 'sucrase', + '...', + 'terser', + 'license', + 'output-base64-worker-script', + ]; const sortKeyA = order.includes(a.name) ? a.name : '...'; const sortKeyB = order.includes(b.name) ? b.name : '...'; diff --git a/package.json b/package.json index 1fd6eb062564..298e17031240 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,8 @@ "dev-packages/clear-cache-gh-action", "dev-packages/external-contributor-gh-action", "dev-packages/rollup-utils", - "dev-packages/node-overhead-gh-action" + "dev-packages/node-overhead-gh-action", + "dev-packages/bundler-tests" ], "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", diff --git a/packages/browser/package.json b/packages/browser/package.json index 5c07b30eaab8..9ac303dbbd95 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -12,19 +12,24 @@ "files": [ "/build/npm" ], - "main": "build/npm/cjs/index.js", - "module": "build/npm/esm/index.js", + "main": "build/npm/cjs/prod/index.js", + "module": "build/npm/esm/prod/index.js", "types": "build/npm/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": { - "types": "./build/npm/types/index.d.ts", - "default": "./build/npm/esm/index.js" + "types": "./build/npm/types/index.d.ts", + "development": { + "import": "./build/npm/esm/dev/index.js", + "require": "./build/npm/cjs/dev/index.js" }, - "require": { - "types": "./build/npm/types/index.d.ts", - "default": "./build/npm/cjs/index.js" + "production": { + "import": "./build/npm/esm/prod/index.js", + "require": "./build/npm/cjs/prod/index.js" + }, + "default": { + "import": "./build/npm/esm/prod/index.js", + "require": "./build/npm/cjs/prod/index.js" } } }, @@ -67,7 +72,7 @@ "clean": "rimraf build coverage .rpt2_cache sentry-browser-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", - "lint:es-compatibility": "es-check es2020 ./build/{bundles,npm/cjs}/*.js && es-check es2020 ./build/npm/esm/*.js --module", + "lint:es-compatibility": "es-check es2020 ./build/{bundles,npm/cjs/prod}/*.js && es-check es2020 ./build/npm/esm/prod/*.js --module", "size:check": "cat build/bundles/bundle.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES2017: \",$1,\"kB\";}'", "test": "vitest run", "test:watch": "vitest --watch", diff --git a/packages/browser/rollup.npm.config.mjs b/packages/browser/rollup.npm.config.mjs index 00251eea81fd..40e3cbff5906 100644 --- a/packages/browser/rollup.npm.config.mjs +++ b/packages/browser/rollup.npm.config.mjs @@ -16,4 +16,5 @@ export default makeNPMConfigVariants( }, }, }), + { splitDevProd: true }, ); diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index dddaa440b198..ea55174f340c 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -63,6 +63,18 @@ type BrowserSpecificOptions = BrowserClientReplayOptions & * @default false */ propagateTraceparent?: boolean; + + /** + * If you use Spotlight by Sentry during development, use + * this option to forward captured Sentry events to Spotlight. + * + * Either set it to true, or provide a specific Spotlight Sidecar URL. + * + * More details: https://spotlightjs.com/ + * + * IMPORTANT: Only set this option to `true` while developing, not in production! + */ + spotlight?: boolean | string; }; /** * Configuration options for the Sentry Browser SDK. diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 7b5d67c636ae..800c1b701352 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -15,6 +15,7 @@ import { browserSessionIntegration } from './integrations/browsersession'; import { globalHandlersIntegration } from './integrations/globalhandlers'; import { httpContextIntegration } from './integrations/httpcontext'; import { linkedErrorsIntegration } from './integrations/linkederrors'; +import { spotlightBrowserIntegration } from './integrations/spotlight'; import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport } from './transports/fetch'; import { checkAndWarnIfIsEmbeddedBrowserExtension } from './utils/detectBrowserExtension'; @@ -90,14 +91,26 @@ export function init(options: BrowserOptions = {}): Client | undefined { const shouldDisableBecauseIsBrowserExtenstion = !options.skipBrowserExtensionCheck && checkAndWarnIfIsEmbeddedBrowserExtension(); + let defaultIntegrations = + options.defaultIntegrations == null ? getDefaultIntegrations(options) : options.defaultIntegrations; + + /* rollup-include-development-only */ + if (options.spotlight) { + if (!defaultIntegrations) { + defaultIntegrations = []; + } + const args = typeof options.spotlight === 'string' ? { sidecarUrl: options.spotlight } : undefined; + defaultIntegrations.push(spotlightBrowserIntegration(args)); + } + /* rollup-include-development-only-end */ + const clientOptions: BrowserClientOptions = { ...options, enabled: shouldDisableBecauseIsBrowserExtenstion ? false : options.enabled, stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser), integrations: getIntegrationsToSetup({ integrations: options.integrations, - defaultIntegrations: - options.defaultIntegrations == null ? getDefaultIntegrations(options) : options.defaultIntegrations, + defaultIntegrations, }), transport: options.transport || makeFetchTransport, }; diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts index 2802bde62fa6..2b6468c3e4ce 100644 --- a/scripts/ci-unit-tests.ts +++ b/scripts/ci-unit-tests.ts @@ -23,6 +23,7 @@ const BROWSER_TEST_PACKAGES = [ '@sentry-internal/replay-canvas', '@sentry-internal/replay-worker', '@sentry-internal/feedback', + '@sentry-internal/bundler-tests', '@sentry/wasm', ]; diff --git a/yarn.lock b/yarn.lock index a49764ebb218..99e434f5efff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6824,105 +6824,115 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz#f768e3b2b0e6b55c595d7a053652c06413713983" - integrity sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w== - -"@rollup/rollup-android-arm64@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz#40379fd5501cfdfd7d8f86dfa1d3ce8d3a609493" - integrity sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ== - -"@rollup/rollup-darwin-arm64@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz#972c227bc89fe8a38a3f0c493e1966900e4e1ff7" - integrity sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg== - -"@rollup/rollup-darwin-x64@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz#96c919dcb87a5aa7dec5f7f77d90de881e578fdd" - integrity sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw== - -"@rollup/rollup-freebsd-arm64@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz#d199d8eaef830179c0c95b7a6e5455e893d1102c" - integrity sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA== - -"@rollup/rollup-freebsd-x64@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz#cab01f9e06ca756c1fabe87d64825ae016af4713" - integrity sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw== - -"@rollup/rollup-linux-arm-gnueabihf@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz#f6f1c42036dba0e58dc2315305429beff0d02c78" - integrity sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ== - -"@rollup/rollup-linux-arm-musleabihf@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz#1157e98e740facf858993fb51431dce3a4a96239" - integrity sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw== - -"@rollup/rollup-linux-arm64-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz#b39db73f8a4c22e7db31a4f3fd45170105f33265" - integrity sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ== - -"@rollup/rollup-linux-arm64-musl@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz#4043398049fe4449c1485312d1ae9ad8af4056dd" - integrity sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g== - -"@rollup/rollup-linux-loongarch64-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz#855a80e7e86490da15a85dcce247dbc25265bc08" - integrity sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew== - -"@rollup/rollup-linux-powerpc64le-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz#8cf843cb7ab1d42e1dda680937cf0a2db6d59047" - integrity sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA== - -"@rollup/rollup-linux-riscv64-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz#287c085472976c8711f16700326f736a527f2f38" - integrity sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw== - -"@rollup/rollup-linux-riscv64-musl@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz#095ad5e53a54ba475979f1b3226b92440c95c892" - integrity sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg== - -"@rollup/rollup-linux-s390x-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz#a3dec8281d8f2aef1703e48ebc65d29fe847933c" - integrity sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw== - -"@rollup/rollup-linux-x64-gnu@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz#4b211e6fd57edd6a134740f4f8e8ea61972ff2c5" - integrity sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw== - -"@rollup/rollup-linux-x64-musl@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz#3ecbf8e21b4157e57bb15dc6837b6db851f9a336" - integrity sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g== - -"@rollup/rollup-win32-arm64-msvc@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz#d4aae38465b2ad200557b53c8c817266a3ddbfd0" - integrity sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg== - -"@rollup/rollup-win32-ia32-msvc@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz#0258e8ca052abd48b23fd6113360fa0cd1ec3e23" - integrity sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A== - -"@rollup/rollup-win32-x64-msvc@4.44.1": - version "4.44.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz#1c982f6a5044ffc2a35cd754a0951bdcb44d5ba0" - integrity sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug== +"@rollup/rollup-android-arm-eabi@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db" + integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ== + +"@rollup/rollup-android-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5" + integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA== + +"@rollup/rollup-darwin-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz#8a102869c88f3780c7d5e6776afd3f19084ecd7f" + integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA== + +"@rollup/rollup-darwin-x64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956" + integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA== + +"@rollup/rollup-freebsd-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899" + integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA== + +"@rollup/rollup-freebsd-x64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10" + integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c" + integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ== + +"@rollup/rollup-linux-arm-musleabihf@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00" + integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ== + +"@rollup/rollup-linux-arm64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc" + integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg== + +"@rollup/rollup-linux-arm64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0" + integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q== + +"@rollup/rollup-linux-loong64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2" + integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA== + +"@rollup/rollup-linux-ppc64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5" + integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw== + +"@rollup/rollup-linux-riscv64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994" + integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw== + +"@rollup/rollup-linux-riscv64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f" + integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg== + +"@rollup/rollup-linux-s390x-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b" + integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ== + +"@rollup/rollup-linux-x64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278" + integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q== + +"@rollup/rollup-linux-x64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350" + integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg== + +"@rollup/rollup-openharmony-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30" + integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw== + +"@rollup/rollup-win32-arm64-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937" + integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w== + +"@rollup/rollup-win32-ia32-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50" + integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg== + +"@rollup/rollup-win32-x64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3" + integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ== + +"@rollup/rollup-win32-x64-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107" + integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg== "@schematics/angular@14.2.13": version "14.2.13" @@ -27073,33 +27083,35 @@ rollup@^3.27.1, rollup@^3.28.1: optionalDependencies: fsevents "~2.3.2" -rollup@^4.20.0, rollup@^4.34.9, rollup@^4.35.0, rollup@^4.44.0: - version "4.44.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.44.1.tgz#641723932894e7acbe6052aea34b8e72ef8b7c8f" - integrity sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg== +rollup@^4.0.0, rollup@^4.20.0, rollup@^4.34.9, rollup@^4.35.0, rollup@^4.44.0: + version "4.52.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.5.tgz#96982cdcaedcdd51b12359981f240f94304ec235" + integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw== dependencies: "@types/estree" "1.0.8" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.44.1" - "@rollup/rollup-android-arm64" "4.44.1" - "@rollup/rollup-darwin-arm64" "4.44.1" - "@rollup/rollup-darwin-x64" "4.44.1" - "@rollup/rollup-freebsd-arm64" "4.44.1" - "@rollup/rollup-freebsd-x64" "4.44.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.44.1" - "@rollup/rollup-linux-arm-musleabihf" "4.44.1" - "@rollup/rollup-linux-arm64-gnu" "4.44.1" - "@rollup/rollup-linux-arm64-musl" "4.44.1" - "@rollup/rollup-linux-loongarch64-gnu" "4.44.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.44.1" - "@rollup/rollup-linux-riscv64-gnu" "4.44.1" - "@rollup/rollup-linux-riscv64-musl" "4.44.1" - "@rollup/rollup-linux-s390x-gnu" "4.44.1" - "@rollup/rollup-linux-x64-gnu" "4.44.1" - "@rollup/rollup-linux-x64-musl" "4.44.1" - "@rollup/rollup-win32-arm64-msvc" "4.44.1" - "@rollup/rollup-win32-ia32-msvc" "4.44.1" - "@rollup/rollup-win32-x64-msvc" "4.44.1" + "@rollup/rollup-android-arm-eabi" "4.52.5" + "@rollup/rollup-android-arm64" "4.52.5" + "@rollup/rollup-darwin-arm64" "4.52.5" + "@rollup/rollup-darwin-x64" "4.52.5" + "@rollup/rollup-freebsd-arm64" "4.52.5" + "@rollup/rollup-freebsd-x64" "4.52.5" + "@rollup/rollup-linux-arm-gnueabihf" "4.52.5" + "@rollup/rollup-linux-arm-musleabihf" "4.52.5" + "@rollup/rollup-linux-arm64-gnu" "4.52.5" + "@rollup/rollup-linux-arm64-musl" "4.52.5" + "@rollup/rollup-linux-loong64-gnu" "4.52.5" + "@rollup/rollup-linux-ppc64-gnu" "4.52.5" + "@rollup/rollup-linux-riscv64-gnu" "4.52.5" + "@rollup/rollup-linux-riscv64-musl" "4.52.5" + "@rollup/rollup-linux-s390x-gnu" "4.52.5" + "@rollup/rollup-linux-x64-gnu" "4.52.5" + "@rollup/rollup-linux-x64-musl" "4.52.5" + "@rollup/rollup-openharmony-arm64" "4.52.5" + "@rollup/rollup-win32-arm64-msvc" "4.52.5" + "@rollup/rollup-win32-ia32-msvc" "4.52.5" + "@rollup/rollup-win32-x64-gnu" "4.52.5" + "@rollup/rollup-win32-x64-msvc" "4.52.5" fsevents "~2.3.2" router@^2.2.0: From 6de4cbad4fd526efd1f017059cf6db9ffc047f9c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 10 Nov 2025 15:32:19 +0100 Subject: [PATCH 03/10] fix(core): Only consider exception mechanism when updating session status from event with exceptions (#18137) This PR fixes an oversight in our session update logic when we update the SDK's session status while processing an error event. Previously, we'd always set `status: 'crashed'` as soon as the top-level `event.level` field was set to `'fatal'` (added in https://github.com/getsentry/sentry-javascript/pull/15072). However, this disregarded the case where the level was initially `'fatal'` but later on the SDK or users decided to set the `event.exceptions.values[i].mechanism.handled` field to `true`. Simplest reproduction: ``` Sentry.captureException(new Error('test'), { captureContext: { level: 'fatal' } }); ``` This PR changes the update logic to only look at the event mechanisms if the event contained exceptions. I _think_ this is still compatible with #15072 because the PR suggests we only care about `event.level === 'fatal'` if no exceptions are on the event. But I'd appreciate a second pair of eyes on this (cc @timfish). --- packages/core/src/client.ts | 6 +- packages/core/test/lib/client.test.ts | 103 ++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index c3ff126732f8..53e0328965a4 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1037,16 +1037,18 @@ export abstract class Client { /** Updates existing session based on the provided event */ protected _updateSessionFromEvent(session: Session, event: Event): void { + // initially, set `crashed` based on the event level and update from exceptions if there are any later on let crashed = event.level === 'fatal'; let errored = false; const exceptions = event.exception?.values; if (exceptions) { errored = true; + // reset crashed to false if there are exceptions, to ensure `mechanism.handled` is respected. + crashed = false; for (const ex of exceptions) { - const mechanism = ex.mechanism; - if (mechanism?.handled === false) { + if (ex.mechanism?.handled === false) { crashed = true; break; } diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index db25793ccf7b..acb4197cf4cf 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'; +import type { SeverityLevel } from '../../src'; import { addBreadcrumb, dsnToString, @@ -2308,6 +2309,108 @@ describe('Client', () => { }); }); + describe('_updateSessionFromEvent()', () => { + describe('event has no exceptions', () => { + it('sets status to crashed if level is fatal', () => { + const client = new TestClient(getDefaultTestClientOptions()); + const session = makeSession(); + getCurrentScope().setSession(session); + + client.captureEvent({ message: 'test', level: 'fatal' }); + + const updatedSession = client.session; + + expect(updatedSession).toMatchObject({ + duration: expect.any(Number), + errors: 1, + init: false, + sid: expect.any(String), + started: expect.any(Number), + status: 'crashed', + timestamp: expect.any(Number), + }); + }); + + it.each(['error', 'warning', 'log', 'info', 'debug'] as const)( + 'sets status to ok if level is %s', + (level: SeverityLevel) => { + const client = new TestClient(getDefaultTestClientOptions()); + const session = makeSession(); + getCurrentScope().setSession(session); + + client.captureEvent({ message: 'test', level }); + + const updatedSession = client.session; + + expect(updatedSession?.status).toEqual('ok'); + }, + ); + }); + + describe('event has exceptions', () => { + it.each(['fatal', 'error', 'warning', 'log', 'info', 'debug'] as const)( + 'sets status ok for handled exceptions and ignores event level %s', + (level: SeverityLevel) => { + const client = new TestClient(getDefaultTestClientOptions()); + const session = makeSession(); + getCurrentScope().setSession(session); + + client.captureException(new Error('test'), { captureContext: { level } }); + + const updatedSession = client.session; + + expect(updatedSession?.status).toEqual('ok'); + }, + ); + + it.each(['fatal', 'error', 'warning', 'log', 'info', 'debug'] as const)( + 'sets status crashed for unhandled exceptions and ignores event level %s', + (level: SeverityLevel) => { + const client = new TestClient(getDefaultTestClientOptions()); + const session = makeSession(); + getCurrentScope().setSession(session); + + client.captureException(new Error('test'), { captureContext: { level }, mechanism: { handled: false } }); + + const updatedSession = client.session; + + expect(updatedSession?.status).toEqual('crashed'); + }, + ); + + it('sets status crashed if at least one exception is unhandled', () => { + const client = new TestClient(getDefaultTestClientOptions()); + const session = makeSession(); + getCurrentScope().setSession(session); + + const event: Event = { + exception: { + values: [ + { + mechanism: { type: 'generic', handled: true }, + }, + { + mechanism: { type: 'generic', handled: false }, + }, + { + mechanism: { type: 'generic', handled: true }, + }, + ], + }, + }; + + client.captureEvent(event); + + const updatedSession = client.session; + + expect(updatedSession).toMatchObject({ + status: 'crashed', + errors: 1, // an event with multiple exceptions still counts as one error in the session + }); + }); + }); + }); + describe('recordDroppedEvent()/_clearOutcomes()', () => { test('records and returns outcomes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); From 02625ef5becdb053c89e86afe481961be8af18f9 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:38:23 +0100 Subject: [PATCH 04/10] ref(browser): Remove truncation when not needed (#18051) Removes the remaining places where we truncate, although not needed. part of https://github.com/getsentry/sentry-javascript/issues/17389 --- packages/browser-utils/src/metrics/lcp.ts | 3 +-- packages/browser/src/integrations/globalhandlers.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/browser-utils/src/metrics/lcp.ts b/packages/browser-utils/src/metrics/lcp.ts index 5f84f1782041..a6410ac08580 100644 --- a/packages/browser-utils/src/metrics/lcp.ts +++ b/packages/browser-utils/src/metrics/lcp.ts @@ -77,8 +77,7 @@ export function _sendStandaloneLcpSpan( entry.element && (attributes['lcp.element'] = htmlTreeAsString(entry.element)); entry.id && (attributes['lcp.id'] = entry.id); - // Trim URL to the first 200 characters. - entry.url && (attributes['lcp.url'] = entry.url.trim().slice(0, 200)); + entry.url && (attributes['lcp.url'] = entry.url); // loadTime is the time of LCP that's related to receiving the LCP element response.. entry.loadTime != null && (attributes['lcp.loadTime'] = entry.loadTime); diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index aa7b2fa9e412..6bada802b98e 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -217,5 +217,5 @@ function getFilenameFromUrl(url: string | undefined): string | undefined { return ``; } - return url.slice(0, 1024); + return url; // it's fine to not truncate it as it's not put in a regex (https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos) } From c236db3f5ac8c9baeb7f7f55b5611a22ffb53039 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 11 Nov 2025 10:07:14 +0100 Subject: [PATCH 05/10] chore(build): Fix incorrect versions after merge (#18154) Whenever we add a new module to the monorepo, the version does not keep up with new releases before it is merged... --- dev-packages/bundler-tests/package.json | 4 ++-- dev-packages/bundler-tests/tests/bundling.test.ts | 4 ++-- dev-packages/bundler-tests/webpack.config.js | 10 ---------- 3 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 dev-packages/bundler-tests/webpack.config.js diff --git a/dev-packages/bundler-tests/package.json b/dev-packages/bundler-tests/package.json index 7cc3dffe89eb..1e61939cac5f 100644 --- a/dev-packages/bundler-tests/package.json +++ b/dev-packages/bundler-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/bundler-tests", - "version": "10.23.0", + "version": "10.24.0", "description": "Bundler tests for Sentry Browser SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bundler-tests", @@ -12,7 +12,7 @@ "test": "vitest run" }, "dependencies": { - "@sentry/browser": "10.23.0", + "@sentry/browser": "10.24.0", "webpack": "^5.0.0", "rollup": "^4.0.0", "vite": "^5.0.0", diff --git a/dev-packages/bundler-tests/tests/bundling.test.ts b/dev-packages/bundler-tests/tests/bundling.test.ts index 1c3f42fc40f4..2cc8113ca83b 100644 --- a/dev-packages/bundler-tests/tests/bundling.test.ts +++ b/dev-packages/bundler-tests/tests/bundling.test.ts @@ -133,12 +133,12 @@ describe('spotlight', () => { for (const [name, bundler] of cases) { test(`${name} development bundle contains spotlight`, async () => { const code = await bundler('development'); - expect(code).includes(SPOTLIGHT_URL); + expect(code).toContain(SPOTLIGHT_URL); }); test(`${name} production bundle does not contain spotlight`, async () => { const code = await bundler('production'); - expect(code).not.includes(SPOTLIGHT_URL); + expect(code).not.toContain(SPOTLIGHT_URL); }); } }); diff --git a/dev-packages/bundler-tests/webpack.config.js b/dev-packages/bundler-tests/webpack.config.js deleted file mode 100644 index 2c3c73b459a4..000000000000 --- a/dev-packages/bundler-tests/webpack.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path'); - -module.exports = (env, argv) => ({ - mode: argv.mode || 'development', - entry: path.resolve(__dirname, 'fixtures/basic/index.js'), - output: { - path: path.resolve(__dirname, 'dist/webpack-' + (argv.mode || 'development')), - filename: 'bundle.js', - }, -}); From c2a53e75fb51cbcebe5b59c5f5af5b01df0783e0 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 11 Nov 2025 10:09:03 +0100 Subject: [PATCH 06/10] feat(core): Truncate request string inputs in OpenAI integration (#18136) The main OpenAI APIs are the [completions](https://platform.openai.com/docs/api-reference/completions/create) and [responses](https://platform.openai.com/docs/api-reference/responses/create) API. Currently we truncate messages sent to the API only if the user input is an array of strings. This works for the completions, but not for the responses API (where the input is a plain string). Updated the truncation logic to also truncate if the input is a plain string (+ test to verify everything works now as expected). --- ...enario-message-truncation-completions.mjs} | 2 +- .../scenario-message-truncation-responses.mjs | 93 +++++++++++++++++++ .../suites/tracing/openai/test.ts | 38 +++++++- .../core/src/utils/ai/messageTruncation.ts | 10 ++ packages/core/src/utils/ai/utils.ts | 4 +- 5 files changed, 143 insertions(+), 4 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/openai/{scenario-message-truncation.mjs => scenario-message-truncation-completions.mjs} (97%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs similarity index 97% rename from dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs rename to dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs index 5623d3763657..96684ed9ec4f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs @@ -12,7 +12,7 @@ class MockOpenAI { await new Promise(resolve => setTimeout(resolve, 10)); return { - id: 'chatcmpl-truncation-test', + id: 'chatcmpl-completions-truncation-test', object: 'chat.completion', created: 1677652288, model: params.model, diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs new file mode 100644 index 000000000000..aebd3341eb33 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs @@ -0,0 +1,93 @@ +import { instrumentOpenAiClient } from '@sentry/core'; +import * as Sentry from '@sentry/node'; + +class MockOpenAI { + constructor(config) { + this.apiKey = config.apiKey; + + this.responses = { + create: async params => { + // Simulate processing time + await new Promise(resolve => setTimeout(resolve, 10)); + + return { + id: 'chatcmpl-responses-truncation-test', + object: 'response', + created_at: 1677652288, + status: 'completed', + error: null, + incomplete_details: null, + instructions: null, + max_output_tokens: null, + model: params.model, + output: [ + { + type: 'message', + id: 'message-123', + status: 'completed', + role: 'assistant', + content: [ + { + type: 'output_text', + text: 'Response to truncated messages', + annotations: [], + }, + ], + }, + ], + parallel_tool_calls: true, + previous_response_id: null, + reasoning: { + effort: null, + summary: null, + }, + store: true, + temperature: params.temperature, + text: { + format: { + type: 'text', + }, + }, + tool_choice: 'auto', + tools: [], + top_p: 1.0, + truncation: 'disabled', + usage: { + input_tokens: 10, + input_tokens_details: { + cached_tokens: 0, + }, + output_tokens: 15, + output_tokens_details: { + reasoning_tokens: 0, + }, + total_tokens: 25, + }, + user: null, + metadata: {}, + }; + }, + }; + } +} + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const mockClient = new MockOpenAI({ + apiKey: 'mock-api-key', + }); + + const client = instrumentOpenAiClient(mockClient); + + // Create 1 large message that gets truncated to fit within the 20KB limit + const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB gets truncated to include only As + + await client.responses.create({ + model: 'gpt-3.5-turbo', + input: largeContent, + temperature: 0.7, + }); + }); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts index 218e3c7ee61f..5cbb27df73bf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -400,7 +400,7 @@ describe('OpenAI integration', () => { createEsmAndCjsTests( __dirname, - 'scenario-message-truncation.mjs', + 'scenario-message-truncation-completions.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { test('truncates messages when they exceed byte limit - keeps only last message and crops it', async () => { @@ -433,4 +433,40 @@ describe('OpenAI integration', () => { }); }, ); + + createEsmAndCjsTests( + __dirname, + 'scenario-message-truncation-responses.mjs', + 'instrument-with-pii.mjs', + (createRunner, test) => { + test('truncates string inputs when they exceed byte limit', async () => { + await createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'main', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.operation.name': 'responses', + 'sentry.op': 'gen_ai.responses', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'gpt-3.5-turbo', + // Messages should be present and should include truncated string input (contains only As) + 'gen_ai.request.messages': expect.stringMatching(/^A+$/), + }), + description: 'responses gpt-3.5-turbo', + op: 'gen_ai.responses', + origin: 'auto.ai.openai', + status: 'ok', + }), + ]), + }, + }) + .start() + .completed(); + }); + }, + ); }); diff --git a/packages/core/src/utils/ai/messageTruncation.ts b/packages/core/src/utils/ai/messageTruncation.ts index 64d186f927b8..945761f6220c 100644 --- a/packages/core/src/utils/ai/messageTruncation.ts +++ b/packages/core/src/utils/ai/messageTruncation.ts @@ -294,3 +294,13 @@ export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): export function truncateGenAiMessages(messages: unknown[]): unknown[] { return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); } + +/** + * Truncate GenAI string input using the default byte limit. + * + * @param input - The string to truncate + * @returns Truncated string + */ +export function truncateGenAiStringInput(input: string): string { + return truncateTextByBytes(input, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); +} diff --git a/packages/core/src/utils/ai/utils.ts b/packages/core/src/utils/ai/utils.ts index 00e147a16e5f..4a7a14eea554 100644 --- a/packages/core/src/utils/ai/utils.ts +++ b/packages/core/src/utils/ai/utils.ts @@ -7,7 +7,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from './gen-ai-attributes'; -import { truncateGenAiMessages } from './messageTruncation'; +import { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation'; /** * Maps AI method paths to Sentry operation name */ @@ -95,7 +95,7 @@ export function setTokenUsageAttributes( export function getTruncatedJsonString(value: T | T[]): string { if (typeof value === 'string') { // Some values are already JSON strings, so we don't need to duplicate the JSON parsing - return value; + return truncateGenAiStringInput(value); } if (Array.isArray(value)) { // truncateGenAiMessages returns an array of strings, so we need to stringify it From b3e65e01fb42fc67862a97d963ccd730bbf635d8 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Nov 2025 13:48:25 +0100 Subject: [PATCH 07/10] feat(cloudflare): Add metrics exports (#18147) closes https://github.com/getsentry/sentry-javascript/issues/18150 --- packages/cloudflare/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index a6aa7ffc8d9a..36de54816030 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -16,6 +16,7 @@ export type { Stacktrace, Thread, User, + Metric, } from '@sentry/core'; export type { CloudflareOptions } from './client'; @@ -100,6 +101,7 @@ export { featureFlagsIntegration, growthbookIntegration, logger, + metrics, } from '@sentry/core'; export { withSentry } from './handler'; From b03617a6e9b4c105a940ac9a93a1af326e41b39c Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Nov 2025 13:48:41 +0100 Subject: [PATCH 08/10] feat(vercel-edge): Add metrics export (#18148) closes https://github.com/getsentry/sentry-javascript/issues/18151 ref https://github.com/getsentry/sentry-javascript/pull/18147 --- packages/vercel-edge/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 7a73234f535e..4f5017bb8f6c 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -16,6 +16,7 @@ export type { Stacktrace, Thread, User, + Metric, } from '@sentry/core'; export type { VercelEdgeOptions } from './types'; @@ -98,6 +99,7 @@ export { createLangChainCallbackHandler, featureFlagsIntegration, logger, + metrics, } from '@sentry/core'; export { VercelEdgeClient } from './client'; From 76fef9805bf4c1b93e5fedfe1de3e62587548598 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Nov 2025 13:48:57 +0100 Subject: [PATCH 09/10] feat(metrics): Add missing metric node exports (#18149) Adds remaining node-based exports for metrics closes https://github.com/getsentry/sentry-javascript/issues/18152 --- .../node-exports-test-app/scripts/consistentExports.ts | 8 -------- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 2 ++ packages/google-cloud-serverless/src/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index ee4b7ac35421..17c6f714c499 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -41,8 +41,6 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Astro 'setupFastifyErrorHandler', - // Todo(metrics): Add metrics exports for beta - 'metrics', ], }, { @@ -56,8 +54,6 @@ const DEPENDENTS: Dependent[] = [ 'childProcessIntegration', 'systemErrorIntegration', 'pinoIntegration', - // Todo(metrics): Add metrics exports for beta - 'metrics', ], }, { @@ -79,8 +75,6 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Serverless 'setupFastifyErrorHandler', - // Todo(metrics): Add metrics exports for beta - 'metrics', ], }, { @@ -90,8 +84,6 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Serverless 'setupFastifyErrorHandler', - // Todo(metrics): Add metrics exports for beta - 'metrics', ], }, { diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 69ca79e04a17..1774f597af43 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -162,6 +162,7 @@ export { statsigIntegration, unleashIntegration, growthbookIntegration, + metrics, } from '@sentry/node'; export { init } from './server/sdk'; diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index da0393d9b0e9..586babab40ee 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -148,6 +148,7 @@ export { statsigIntegration, unleashIntegration, growthbookIntegration, + metrics, } from '@sentry/node'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 33af15790191..813e087dc2d2 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -16,6 +16,7 @@ export type { Thread, User, FeatureFlagsIntegration, + Metric, } from '@sentry/core'; export { @@ -164,6 +165,7 @@ export { OpenFeatureIntegrationHook, statsigIntegration, unleashIntegration, + metrics, } from '@sentry/node'; export { diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 02e55c45a7ba..3aa7da1cbab9 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -148,6 +148,7 @@ export { OpenFeatureIntegrationHook, statsigIntegration, unleashIntegration, + metrics, } from '@sentry/node'; export { diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 0613d6397f96..88177f11354a 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -128,6 +128,7 @@ export { createConsolaReporter, createSentryWinstonTransport, vercelAIIntegration, + metrics, } from '@sentry/node'; // We can still leave this for the carrier init and type exports From a730d96b04fadbc130e3865a26bc0249324aaebf Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Nov 2025 14:24:13 +0100 Subject: [PATCH 10/10] meta(changelog): Update changelog for 10.25.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f98b440d47d..d5cf4d9b810f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.25.0 + +- feat(browser): Include Spotlight in development bundles ([#18078](https://github.com/getsentry/sentry-javascript/pull/18078)) +- feat(cloudflare): Add metrics exports ([#18147](https://github.com/getsentry/sentry-javascript/pull/18147)) +- feat(core): Truncate request string inputs in OpenAI integration ([#18136](https://github.com/getsentry/sentry-javascript/pull/18136)) +- feat(metrics): Add missing metric node exports ([#18149](https://github.com/getsentry/sentry-javascript/pull/18149)) +- feat(node): Add `maxCacheKeyLength` to Redis integration (remove truncation) ([#18045](https://github.com/getsentry/sentry-javascript/pull/18045)) +- feat(vercel-edge): Add metrics export ([#18148](https://github.com/getsentry/sentry-javascript/pull/18148)) +- fix(core): Only consider exception mechanism when updating session status from event with exceptions ([#18137](https://github.com/getsentry/sentry-javascript/pull/18137)) +- ref(browser): Remove truncation when not needed ([#18051](https://github.com/getsentry/sentry-javascript/pull/18051)) + +
+ Internal Changes + +- chore(build): Fix incorrect versions after merge ([#18154](https://github.com/getsentry/sentry-javascript/pull/18154)) +
+ ## 10.24.0 ### Important Changes