From 74ee2fe6ef7394d7eea8e1427970e561b526a12d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 21 Apr 2026 18:46:15 +0200 Subject: [PATCH 01/18] feat(core): Support homogeneous primitive arrays as span attributes Relay's wire contract (AttributeType enum in relay-event-schema) defines exactly five `type:` tags: boolean, integer, double, string, array. The SDK's AttributeTypeMap previously declared typed array variants (`string[]`, `integer[]`, etc.) that Relay does not recognize - these were never actually emitted because the runtime serializer only handled primitives, so array-valued attributes silently dropped. This change: - Collapses the four `x[]` variants in AttributeTypeMap into a single `array` variant whose value is `Array | Array | Array`. - Extends getTypedAttributeValue to auto-detect homogeneous primitive arrays and emit `{type: 'array', value: [...]}`. - Adds an isHomogeneousPrimitiveArray guard so mixed-type and nested arrays remain unsupported (dropped by default, stringified under the fallback path). - Updates tests to cover the new typed-array path (including empty arrays, unit preservation, and mixed-type drop/stringify). Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/attributes.ts | 33 ++++--- packages/core/test/lib/attributes.test.ts | 90 +++++++++---------- packages/core/test/lib/logs/internal.test.ts | 10 ++- .../lib/tracing/spans/estimateSize.test.ts | 6 +- .../core/test/lib/utils/spanUtils.test.ts | 4 +- 5 files changed, 76 insertions(+), 67 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index 1f4a6638f577..c8681fc6e757 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -15,10 +15,7 @@ type AttributeTypeMap = { integer: number; double: number; boolean: boolean; - 'string[]': Array; - 'integer[]': Array; - 'double[]': Array; - 'boolean[]': Array; + array: Array | Array | Array; }; /* Generates a type from the AttributeTypeMap like: @@ -66,9 +63,9 @@ export function isAttributeObject(maybeObj: unknown): maybeObj is AttributeObjec /** * Converts an attribute value to a typed attribute value. * - * For now, we intentionally only support primitive values and attribute objects with primitive values. - * If @param useFallback is true, we stringify non-primitive values to a string attribute value. Otherwise - * we return `undefined` for unsupported values. + * For now, we support primitive values and homogeneous arrays of primitives, either raw or + * inside attribute objects. If @param useFallback is true, we stringify other non-primitive values + * to a string attribute value. Otherwise we return `undefined` for unsupported values. * * @param value - The value of the passed attribute. * @param useFallback - If true, unsupported values will be stringified to a string attribute value. @@ -170,17 +167,18 @@ function estimatePrimitiveSizeInBytes(value: Primitive): number { } /** - * NOTE: We intentionally do not return anything for non-primitive values: - * - array support will come in the future but if we stringify arrays now, - * sending arrays (unstringified) later will be a subtle breaking change. + * NOTE: We return typed attributes for primitives and homogeneous arrays of primitives: + * - Homogeneous primitive arrays ship with `type: 'array'` (Relay's wire tag for arrays). + * - Mixed-type and nested arrays are not supported and return undefined. * - Objects are not supported yet and product support is still TBD. * - We still keep the type signature for TypedAttributeValue wider to avoid a - * breaking change once we add support for non-primitive values. - * - Once we go back to supporting arrays and stringifying all other values, - * we already implemented the serialization logic here: - * https://github.com/getsentry/sentry-javascript/pull/18165 + * breaking change once we add support for other non-primitive values. */ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { + if (Array.isArray(value) && isHomogeneousPrimitiveArray(value)) { + return { value, type: 'array' }; + } + const primitiveType = typeof value === 'string' ? 'string' @@ -201,3 +199,10 @@ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { return { value, type: primitiveType }; } } + +function isHomogeneousPrimitiveArray(arr: unknown[]): boolean { + if (arr.length === 0) return true; + const t = typeof arr[0]; + if (t !== 'string' && t !== 'number' && t !== 'boolean') return false; + return arr.every(v => typeof v === t); +} diff --git a/packages/core/test/lib/attributes.test.ts b/packages/core/test/lib/attributes.test.ts index 13b9e026e6e9..060fbe1f618b 100644 --- a/packages/core/test/lib/attributes.test.ts +++ b/packages/core/test/lib/attributes.test.ts @@ -76,33 +76,38 @@ describe('attributeValueToTypedAttributeValue', () => { ); }); - describe('invalid values (non-primitives)', () => { - it.each([ - ['foo', 'bar'], - [1, 2, 3], - [true, false, true], - [1, 'foo', true], - { foo: 'bar' }, - () => 'test', - Symbol('test'), - ])('returns undefined for non-primitive raw values (%s)', value => { - const result = attributeValueToTypedAttributeValue(value); - expect(result).toBeUndefined(); - }); + describe('homogeneous primitive arrays', () => { + it.each([[['foo', 'bar']], [[1, 2, 3]], [[true, false, true]], [[] as unknown[]]])( + 'emits a typed array attribute for raw value %j', + value => { + const result = attributeValueToTypedAttributeValue(value); + expect(result).toStrictEqual({ value, type: 'array' }); + }, + ); - it.each([ - ['foo', 'bar'], - [1, 2, 3], - [true, false, true], - [1, 'foo', true], - { foo: 'bar' }, - () => 'test', - Symbol('test'), - ])('returns undefined for non-primitive attribute object values (%s)', value => { - const result = attributeValueToTypedAttributeValue({ value }); - expect(result).toBeUndefined(); + it('emits a typed array attribute for attribute object values', () => { + const result = attributeValueToTypedAttributeValue({ value: ['foo', 'bar'] }); + expect(result).toStrictEqual({ value: ['foo', 'bar'], type: 'array' }); }); }); + + describe('invalid values (non-primitives)', () => { + it.each([[[1, 'foo', true]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + 'returns undefined for non-primitive raw values (%s)', + value => { + const result = attributeValueToTypedAttributeValue(value); + expect(result).toBeUndefined(); + }, + ); + + it.each([[[1, 'foo', true]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + 'returns undefined for non-primitive attribute object values (%s)', + value => { + const result = attributeValueToTypedAttributeValue({ value }); + expect(result).toBeUndefined(); + }, + ); + }); }); describe('with fallback=true', () => { @@ -189,26 +194,10 @@ describe('attributeValueToTypedAttributeValue', () => { }); describe('invalid values (non-primitives) - stringified fallback', () => { - it('stringifies string arrays', () => { - const result = attributeValueToTypedAttributeValue(['foo', 'bar'], true); + it('stringifies mixed-type arrays (not homogeneous)', () => { + const result = attributeValueToTypedAttributeValue(['foo', 1, true], true); expect(result).toStrictEqual({ - value: '["foo","bar"]', - type: 'string', - }); - }); - - it('stringifies number arrays', () => { - const result = attributeValueToTypedAttributeValue([1, 2, 3], true); - expect(result).toStrictEqual({ - value: '[1,2,3]', - type: 'string', - }); - }); - - it('stringifies boolean arrays', () => { - const result = attributeValueToTypedAttributeValue([true, false, true], true); - expect(result).toStrictEqual({ - value: '[true,false,true]', + value: '["foo",1,true]', type: 'string', }); }); @@ -425,15 +414,17 @@ describe('serializeAttributes', () => { describe('invalid (non-primitive) values', () => { it("doesn't fall back to stringification by default", () => { const result = serializeAttributes({ foo: { some: 'object' }, bar: [1, 2, 3], baz: () => {} }); - expect(result).toStrictEqual({}); + expect(result).toStrictEqual({ + bar: { type: 'array', value: [1, 2, 3] }, + }); }); it('falls back to stringification of unsupported non-primitive values if fallback is true', () => { const result = serializeAttributes({ foo: { some: 'object' }, bar: [1, 2, 3], baz: () => {} }, true); expect(result).toStrictEqual({ bar: { - type: 'string', - value: '[1,2,3]', + type: 'array', + value: [1, 2, 3], }, baz: { type: 'string', @@ -445,5 +436,12 @@ describe('serializeAttributes', () => { }, }); }); + + it('drops mixed-type arrays by default and stringifies them with fallback', () => { + expect(serializeAttributes({ mixed: ['a', 1] })).toStrictEqual({}); + expect(serializeAttributes({ mixed: ['a', 1] }, true)).toStrictEqual({ + mixed: { type: 'string', value: '["a",1]' }, + }); + }); }); }); diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 360485f5ca84..48c93c7cf1d1 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -191,7 +191,6 @@ describe('_INTERNAL_captureLog', () => { scope.setAttribute('scope_2', { value: 38, unit: 'gigabyte' }); scope.setAttributes({ scope_3: true, - // these are invalid since for now we don't support arrays scope_4: [1, 2, 3], scope_5: { value: [true, false, true], unit: 'second' }, }); @@ -229,6 +228,15 @@ describe('_INTERNAL_captureLog', () => { type: 'boolean', value: true, }, + scope_4: { + type: 'array', + value: [1, 2, 3], + }, + scope_5: { + type: 'array', + value: [true, false, true], + unit: 'second', + }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); diff --git a/packages/core/test/lib/tracing/spans/estimateSize.test.ts b/packages/core/test/lib/tracing/spans/estimateSize.test.ts index 35d569691dea..e92b260839f2 100644 --- a/packages/core/test/lib/tracing/spans/estimateSize.test.ts +++ b/packages/core/test/lib/tracing/spans/estimateSize.test.ts @@ -130,9 +130,9 @@ describe('estimateSerializedSpanSizeInBytes', () => { status: 'ok', is_segment: false, attributes: { - 'item.ids': { type: 'string[]', value: ['id-001', 'id-002', 'id-003', 'id-004', 'id-005'] }, - scores: { type: 'double[]', value: [1.1, 2.2, 3.3, 4.4] }, - flags: { type: 'boolean[]', value: [true, false, true] }, + 'item.ids': { type: 'array', value: ['id-001', 'id-002', 'id-003', 'id-004', 'id-005'] }, + scores: { type: 'array', value: [1.1, 2.2, 3.3, 4.4] }, + flags: { type: 'array', value: [true, false, true] }, }, }; diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index e4a0b31990d7..a2f2dbea7aba 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -622,11 +622,9 @@ describe('spanToJSON', () => { attr1: { type: 'string', value: 'value1' }, attr2: { type: 'integer', value: 2 }, attr3: { type: 'boolean', value: true }, + attr4: { type: 'array', value: [1, 2, 3] }, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: { type: 'string', value: 'test op' }, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: { type: 'string', value: 'auto' }, - // notice the absence of `attr4`! - // for now, we don't yet serialize array attributes. This test will fail - // once we allow serializing them. }, links: [ { From 2d84168c9cbdc4a51416eb169f1d5f05e5c14b05 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 21 Apr 2026 19:28:48 +0200 Subject: [PATCH 02/18] test: Update integration tests for array attribute support Arrays that were previously dropped by the serializer now ship as native array attributes (`type: 'array'`). Update the affected integration test expectations and bump size-limit thresholds for the five bundle scenarios whose gzipped/uncompressed sizes grew from the new serializer logic. Co-Authored-By: Claude Opus 4.7 (1M context) --- .size-limit.js | 10 +++++----- .../suites/public-api/logger/integration/test.ts | 2 +- .../public-api/logger/scopeAttributes/subject.js | 1 - .../suites/public-api/logger/scopeAttributes/test.ts | 4 ++++ .../suites/public-api/logger/scenario.ts | 1 - .../suites/public-api/logger/test.ts | 4 ++++ 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index c4b37635ecda..ce52d17deeed 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -138,7 +138,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'metrics', 'logger'), gzip: true, - limit: '28 KB', + limit: '29 KB', }, // React SDK (ESM) { @@ -215,13 +215,13 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay)', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: true, - limit: '82 KB', + limit: '83 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)', path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: true, - limit: '83 KB', + limit: '84 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback)', @@ -283,7 +283,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '255 KB', + limit: '256 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed', @@ -297,7 +297,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '268 KB', + limit: '269 KB', }, // Next.js SDK (ESM) { diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts index 7315e8cf4f36..1d969682bd3b 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts @@ -177,7 +177,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 'prefix', type: 'string' }, 'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' }, - 'sentry.message.parameter.2': { value: '[4,5,6]', type: 'string' }, + 'sentry.message.parameter.2': { value: [4, 5, 6], type: 'array' }, 'sentry.message.parameter.3': { value: 'suffix', type: 'string' }, }, }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/subject.js b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/subject.js index 9bba2c222bdc..76377beb82a7 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/subject.js @@ -3,7 +3,6 @@ Sentry.logger.info('log_before_any_scope', { log_attr: 'log_attr_1' }); Sentry.getGlobalScope().setAttributes({ global_scope_attr: true }); -// this attribute will not be sent for now Sentry.getGlobalScope().setAttribute('array_attr', [1, 2, 3]); // global scope, log attribute diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts index 4d7970945436..1b45e90c0613 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts @@ -47,6 +47,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, log_attr: { value: 'log_attr_2', type: 'string' }, }, }, @@ -61,6 +62,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_3', type: 'string' }, }, @@ -76,6 +78,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, scope_attr: { value: 200, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_4', type: 'string' }, @@ -92,6 +95,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_5', type: 'string' }, diff --git a/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts index c3be917706e7..c3ad84c1e146 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts @@ -15,7 +15,6 @@ async function run(): Promise { Sentry.getGlobalScope().setAttribute('global_scope_attr', true); - // this attribute will not be sent for now Sentry.getGlobalScope().setAttributes({ array_attr: [1, 2, 3] }); // global scope, log attribute diff --git a/dev-packages/node-integration-tests/suites/public-api/logger/test.ts b/dev-packages/node-integration-tests/suites/public-api/logger/test.ts index 6b9f43e738d2..a8bc88f27bcb 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logger/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logger/test.ts @@ -60,6 +60,7 @@ describe('logs', () => { attributes: { ...commonAttributes, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, log_attr: { value: 'log_attr_2', type: 'string' }, }, }, @@ -72,6 +73,7 @@ describe('logs', () => { attributes: { ...commonAttributes, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_3', type: 'string' }, }, @@ -85,6 +87,7 @@ describe('logs', () => { attributes: { ...commonAttributes, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, scope_attr: { value: 200, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_4', type: 'string' }, @@ -99,6 +102,7 @@ describe('logs', () => { attributes: { ...commonAttributes, global_scope_attr: { value: true, type: 'boolean' }, + array_attr: { value: [1, 2, 3], type: 'array' }, isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' }, scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' }, log_attr: { value: 'log_attr_5', type: 'string' }, From fa70b75914297a7fd8396292b0e06e390f0e54f0 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 21 Apr 2026 19:31:17 +0200 Subject: [PATCH 03/18] Revert ".size-limit.js" bumps Drop the size-limit increases for the five bundle scenarios that grew from adding homogeneous primitive array support. Test expectation updates from the previous commit stay. Co-Authored-By: Claude Opus 4.7 (1M context) --- .size-limit.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index ce52d17deeed..c4b37635ecda 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -138,7 +138,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'metrics', 'logger'), gzip: true, - limit: '29 KB', + limit: '28 KB', }, // React SDK (ESM) { @@ -215,13 +215,13 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay)', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: true, - limit: '83 KB', + limit: '82 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)', path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: true, - limit: '84 KB', + limit: '83 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback)', @@ -283,7 +283,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '256 KB', + limit: '255 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed', @@ -297,7 +297,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '269 KB', + limit: '268 KB', }, // Next.js SDK (ESM) { From a71058e08879dd7b9e6de0452709709f065fd771 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 21 Apr 2026 20:13:50 +0200 Subject: [PATCH 04/18] chore(size-limit): Bump thresholds for array-attribute support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-apply the size-limit bumps needed to account for the new homogeneous-primitive-array detection logic. Five scenarios grew past their thresholds: - @sentry/browser (incl. Metrics & Logs): 28 → 29 KB - CDN Bundle (incl. Logs, Metrics): 30 → 31 KB - CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed: 258.5 → 259 KB - CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed: 268 → 269 KB - CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed: 271.5 → 272 KB Co-Authored-By: Claude Opus 4.7 (1M context) --- .size-limit.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index cad516a0a49a..1c4eebbbb275 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -138,7 +138,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'metrics', 'logger'), gzip: true, - limit: '28 KB', + limit: '29 KB', }, // React SDK (ESM) { @@ -197,7 +197,7 @@ module.exports = [ name: 'CDN Bundle (incl. Logs, Metrics)', path: createCDNPath('bundle.logs.metrics.min.js'), gzip: true, - limit: '30 KB', + limit: '31 KB', }, { name: 'CDN Bundle (incl. Tracing, Logs, Metrics)', @@ -283,21 +283,21 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '258.5 KB', + limit: '259 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed', path: createCDNPath('bundle.tracing.replay.feedback.min.js'), gzip: false, brotli: false, - limit: '268 KB', + limit: '269 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed', path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '271.5 KB', + limit: '272 KB', }, // Next.js SDK (ESM) { From 467dfcc8c77b9cc1df6e9d904f6bdba147549c7f Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 28 Apr 2026 11:41:15 +0200 Subject: [PATCH 05/18] types --- packages/core/src/attributes.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index c8681fc6e757..9a8a318775b4 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -175,7 +175,7 @@ function estimatePrimitiveSizeInBytes(value: Primitive): number { * breaking change once we add support for other non-primitive values. */ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { - if (Array.isArray(value) && isHomogeneousPrimitiveArray(value)) { + if (isHomogeneousPrimitiveArray(value)) { return { value, type: 'array' }; } @@ -200,7 +200,10 @@ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { } } -function isHomogeneousPrimitiveArray(arr: unknown[]): boolean { +function isHomogeneousPrimitiveArray( + arr: unknown, +): arr is Array | Array | Array { + if (!Array.isArray(arr)) return false; if (arr.length === 0) return true; const t = typeof arr[0]; if (t !== 'string' && t !== 'number' && t !== 'boolean') return false; From 4b055965a0c87131decc0b6208f7dffb575bd353 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 28 Apr 2026 11:48:07 +0200 Subject: [PATCH 06/18] formatting --- packages/core/src/attributes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index 9a8a318775b4..c259e1315d14 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -200,9 +200,7 @@ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { } } -function isHomogeneousPrimitiveArray( - arr: unknown, -): arr is Array | Array | Array { +function isHomogeneousPrimitiveArray(arr: unknown): arr is Array | Array | Array { if (!Array.isArray(arr)) return false; if (arr.length === 0) return true; const t = typeof arr[0]; From 7cd226a8fe95faaadd8762eaef680811bccc1e98 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 28 Apr 2026 13:34:10 +0200 Subject: [PATCH 07/18] guard against nan --- packages/core/src/attributes.ts | 2 +- packages/core/test/lib/attributes.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index c259e1315d14..81a7b3dbb992 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -205,5 +205,5 @@ function isHomogeneousPrimitiveArray(arr: unknown): arr is Array | Array if (arr.length === 0) return true; const t = typeof arr[0]; if (t !== 'string' && t !== 'number' && t !== 'boolean') return false; - return arr.every(v => typeof v === t); + return arr.every(v => typeof v === t && (t !== 'number' || !Number.isNaN(v))); } diff --git a/packages/core/test/lib/attributes.test.ts b/packages/core/test/lib/attributes.test.ts index 060fbe1f618b..e0cb2848afac 100644 --- a/packages/core/test/lib/attributes.test.ts +++ b/packages/core/test/lib/attributes.test.ts @@ -92,7 +92,7 @@ describe('attributeValueToTypedAttributeValue', () => { }); describe('invalid values (non-primitives)', () => { - it.each([[[1, 'foo', true]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + it.each([[[1, 'foo', true]], [[NaN, 1, 2]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( 'returns undefined for non-primitive raw values (%s)', value => { const result = attributeValueToTypedAttributeValue(value); @@ -100,7 +100,7 @@ describe('attributeValueToTypedAttributeValue', () => { }, ); - it.each([[[1, 'foo', true]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + it.each([[[1, 'foo', true]], [[NaN, 1, 2]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( 'returns undefined for non-primitive attribute object values (%s)', value => { const result = attributeValueToTypedAttributeValue({ value }); From 735bbe889a341a78db3ee536e2a5c52a1ba9f484 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 28 Apr 2026 13:55:41 +0200 Subject: [PATCH 08/18] chore(size-limit): Bump CDN Tracing+Replay+Feedback uncompressed to 272 KB Co-Authored-By: Claude Opus 4.7 (1M context) --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 38880cc91247..18580bf7b182 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -326,7 +326,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.feedback.min.js'), gzip: false, brotli: false, - limit: '271 KB', + limit: '272 KB', disablePlugins: ['@size-limit/esbuild'], }, { From a26bdc06ec32dba76c3b6d704823b71dd8ee42f5 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 13:57:35 +0200 Subject: [PATCH 09/18] no runtime validation --- packages/core/src/attributes.ts | 37 +++++++-------- packages/core/test/lib/attributes.test.ts | 55 +++++++++-------------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index 81a7b3dbb992..a93968addc69 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -8,14 +8,14 @@ export type RawAttribute = T extends { value: any } | { unit: any } ? Attribu export type Attributes = Record; -export type AttributeValueType = string | number | boolean | Array | Array | Array; +export type AttributeValueType = string | number | boolean | Array; type AttributeTypeMap = { string: string; integer: number; double: number; boolean: boolean; - array: Array | Array | Array; + array: Array; }; /* Generates a type from the AttributeTypeMap like: @@ -63,9 +63,9 @@ export function isAttributeObject(maybeObj: unknown): maybeObj is AttributeObjec /** * Converts an attribute value to a typed attribute value. * - * For now, we support primitive values and homogeneous arrays of primitives, either raw or - * inside attribute objects. If @param useFallback is true, we stringify other non-primitive values - * to a string attribute value. Otherwise we return `undefined` for unsupported values. + * For now, we support primitive values and arrays, either raw or inside attribute objects. + * If @param useFallback is true, we stringify other non-primitive values to a string attribute + * value. Otherwise we return `undefined` for unsupported values. * * @param value - The value of the passed attribute. * @param useFallback - If true, unsupported values will be stringified to a string attribute value. @@ -135,21 +135,27 @@ export function estimateTypedAttributesSizeInBytes(attributes: Attributes | unde return 0; } let weight = 0; + const fallbackWeight = 100; + for (const [key, attr] of Object.entries(attributes)) { weight += key.length * 2; weight += attr.type.length * 2; weight += (attr.unit?.length ?? 0) * 2; const val = attr.value; - if (Array.isArray(val)) { + if (Array.isArray(val) && isPrimitive(val[0])) { // Assumption: Individual array items have the same type and roughly the same size // probably not always true but allows us to cut down on runtime weight += estimatePrimitiveSizeInBytes(val[0]) * val.length; + } else if (Array.isArray(val) && !isPrimitive(val[0])) { + // Fallback for arrays with non-primitive values (e.g. objects) + // Again (to cut down on runtime), we intentionally don't traverse the full hierarchy + weight += fallbackWeight * val.length; } else if (isPrimitive(val)) { weight += estimatePrimitiveSizeInBytes(val); } else { // default fallback for anything else (objects) - weight += 100; + weight += fallbackWeight; } } return weight; @@ -167,15 +173,12 @@ function estimatePrimitiveSizeInBytes(value: Primitive): number { } /** - * NOTE: We return typed attributes for primitives and homogeneous arrays of primitives: - * - Homogeneous primitive arrays ship with `type: 'array'` (Relay's wire tag for arrays). - * - Mixed-type and nested arrays are not supported and return undefined. + * NOTE: We return typed attributes for primitives and arrays: + * - Relay currently only supports arrays consisting of primitive values. Attributes with non-conforming arrays are dropped by Relay, so runtime type validation in the SDK is unnecessary. * - Objects are not supported yet and product support is still TBD. - * - We still keep the type signature for TypedAttributeValue wider to avoid a - * breaking change once we add support for other non-primitive values. */ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { - if (isHomogeneousPrimitiveArray(value)) { + if (Array.isArray(value) && value.length !== 0) { return { value, type: 'array' }; } @@ -199,11 +202,3 @@ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void { return { value, type: primitiveType }; } } - -function isHomogeneousPrimitiveArray(arr: unknown): arr is Array | Array | Array { - if (!Array.isArray(arr)) return false; - if (arr.length === 0) return true; - const t = typeof arr[0]; - if (t !== 'string' && t !== 'number' && t !== 'boolean') return false; - return arr.every(v => typeof v === t && (t !== 'number' || !Number.isNaN(v))); -} diff --git a/packages/core/test/lib/attributes.test.ts b/packages/core/test/lib/attributes.test.ts index e0cb2848afac..a42bf272d335 100644 --- a/packages/core/test/lib/attributes.test.ts +++ b/packages/core/test/lib/attributes.test.ts @@ -76,23 +76,28 @@ describe('attributeValueToTypedAttributeValue', () => { ); }); - describe('homogeneous primitive arrays', () => { - it.each([[['foo', 'bar']], [[1, 2, 3]], [[true, false, true]], [[] as unknown[]]])( - 'emits a typed array attribute for raw value %j', - value => { - const result = attributeValueToTypedAttributeValue(value); - expect(result).toStrictEqual({ value, type: 'array' }); - }, - ); + // Element types are not validated at runtime — Relay drops non-conforming arrays. + describe('arrays', () => { + it.each([ + [['foo', 'bar']], + [[1, 2, 3]], + [[true, false, true]], + [[1, 'foo', true]], + [[NaN, 1, 2]], + [{ value: ['foo', 'bar'] }], + ])('emits a typed array attribute for value %j', value => { + const result = attributeValueToTypedAttributeValue(value); + const expected = Array.isArray(value) ? value : (value as { value: unknown[] }).value; + expect(result).toStrictEqual({ value: expected, type: 'array' }); + }); - it('emits a typed array attribute for attribute object values', () => { - const result = attributeValueToTypedAttributeValue({ value: ['foo', 'bar'] }); - expect(result).toStrictEqual({ value: ['foo', 'bar'], type: 'array' }); + it('returns undefined for empty arrays', () => { + expect(attributeValueToTypedAttributeValue([])).toBeUndefined(); }); }); describe('invalid values (non-primitives)', () => { - it.each([[[1, 'foo', true]], [[NaN, 1, 2]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + it.each([[{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( 'returns undefined for non-primitive raw values (%s)', value => { const result = attributeValueToTypedAttributeValue(value); @@ -100,7 +105,7 @@ describe('attributeValueToTypedAttributeValue', () => { }, ); - it.each([[[1, 'foo', true]], [[NaN, 1, 2]], [{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( + it.each([[{ foo: 'bar' }], [() => 'test'], [Symbol('test')]])( 'returns undefined for non-primitive attribute object values (%s)', value => { const result = attributeValueToTypedAttributeValue({ value }); @@ -194,22 +199,6 @@ describe('attributeValueToTypedAttributeValue', () => { }); describe('invalid values (non-primitives) - stringified fallback', () => { - it('stringifies mixed-type arrays (not homogeneous)', () => { - const result = attributeValueToTypedAttributeValue(['foo', 1, true], true); - expect(result).toStrictEqual({ - value: '["foo",1,true]', - type: 'string', - }); - }); - - it('stringifies mixed arrays', () => { - const result = attributeValueToTypedAttributeValue([1, 'foo', true], true); - expect(result).toStrictEqual({ - value: '[1,"foo",true]', - type: 'string', - }); - }); - it('stringifies objects', () => { const result = attributeValueToTypedAttributeValue({ foo: 'bar' }, true); expect(result).toStrictEqual({ @@ -437,10 +426,10 @@ describe('serializeAttributes', () => { }); }); - it('drops mixed-type arrays by default and stringifies them with fallback', () => { - expect(serializeAttributes({ mixed: ['a', 1] })).toStrictEqual({}); - expect(serializeAttributes({ mixed: ['a', 1] }, true)).toStrictEqual({ - mixed: { type: 'string', value: '["a",1]' }, + // Element types are not validated at runtime — Relay drops non-conforming arrays. + it('accepts mixed-type arrays', () => { + expect(serializeAttributes({ mixed: ['a', 1] })).toStrictEqual({ + mixed: { type: 'array', value: ['a', 1] }, }); }); }); From cb94756772eb576929f576fcfcd048c29abcd1d3 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 14:07:51 +0200 Subject: [PATCH 10/18] add changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0658797b2093..c06b79e36273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **feat(core): Support array attributes for spans, logs, and metrics ([#20427](https://github.com/getsentry/sentry-javascript/pull/20427))** + + Arrays of primitive values (`string`, `number`, `boolean`) are now accepted as attribute values. Arrays containing non-primitive elements will be dropped and won't show up in Sentry. + - **feat(browser): Add `ingest_settings` to v2 log envelope payload ([#20453](https://github.com/getsentry/sentry-javascript/pull/20453))** Inference of user data (e.g. IP address, browser name/version) on log events is now gated behind the `sendDefaultPii` option. Previously, this data was always inferred by default. From fa0dbdec031d50d533bdec1b0b0d7235d79f9ca5 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 14:30:40 +0200 Subject: [PATCH 11/18] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c06b79e36273..52fec9f57892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - **feat(core): Support array attributes for spans, logs, and metrics ([#20427](https://github.com/getsentry/sentry-javascript/pull/20427))** - Arrays of primitive values (`string`, `number`, `boolean`) are now accepted as attribute values. Arrays containing non-primitive elements will be dropped and won't show up in Sentry. + Arrays of primitive values (`string`, `number`, `boolean`) are now accepted as attribute values. Arrays containing non-primitive elements will be dropped and won't show up in Sentry. Note that array attributes on logs and metrics were previously stringified in certain cases and will now be sent as arrays instead. - **feat(browser): Add `ingest_settings` to v2 log envelope payload ([#20453](https://github.com/getsentry/sentry-javascript/pull/20453))** From c7117035557cde7c5d4ea4015e1c74e82a04164a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 14:38:10 +0200 Subject: [PATCH 12/18] . --- packages/core/test/lib/attributes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/lib/attributes.test.ts b/packages/core/test/lib/attributes.test.ts index a42bf272d335..4a37782851f4 100644 --- a/packages/core/test/lib/attributes.test.ts +++ b/packages/core/test/lib/attributes.test.ts @@ -76,7 +76,7 @@ describe('attributeValueToTypedAttributeValue', () => { ); }); - // Element types are not validated at runtime — Relay drops non-conforming arrays. + // Element types are not validated at runtime by the SDK (Relay drops non-conforming arrays). describe('arrays', () => { it.each([ [['foo', 'bar']], From ad724e2f5b99df4340a0df2e796e9d245caf425a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 14:43:37 +0200 Subject: [PATCH 13/18] . --- packages/core/test/lib/attributes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/lib/attributes.test.ts b/packages/core/test/lib/attributes.test.ts index 4a37782851f4..6c936e8679a5 100644 --- a/packages/core/test/lib/attributes.test.ts +++ b/packages/core/test/lib/attributes.test.ts @@ -426,7 +426,7 @@ describe('serializeAttributes', () => { }); }); - // Element types are not validated at runtime — Relay drops non-conforming arrays. + // Element types are not validated at runtime by the SDK (Relay drops non-conforming arrays). it('accepts mixed-type arrays', () => { expect(serializeAttributes({ mixed: ['a', 1] })).toStrictEqual({ mixed: { type: 'array', value: ['a', 1] }, From 01a68513b8728ade8bb5fa40ab90c86ef4f76e81 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 15:42:38 +0200 Subject: [PATCH 14/18] fix test --- .../suites/public-api/logger/integration/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts index 07ec4c38d05d..f16fc7170120 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts @@ -162,7 +162,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Array: {}', type: 'string' }, - 'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' }, + 'sentry.message.parameter.0': { value: [1, 2, 3, 'string'], type: 'array' }, }, }, { From f896f3a3599c6b39a8448d41798d9f865c08466c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 16:03:06 +0200 Subject: [PATCH 15/18] fix test --- .../test-applications/deno-streamed/tests/spans.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/deno-streamed/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/deno-streamed/tests/spans.test.ts index 15d7eaf99d9a..11f87c9229db 100644 --- a/dev-packages/e2e-tests/test-applications/deno-streamed/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno-streamed/tests/spans.test.ts @@ -15,7 +15,10 @@ const SEGMENT_SPAN = { type: 'integer', value: expect.any(Number), }, - // TODO: 'device.archs' is set but arrays are not yet serialized in span attributes + 'device.archs': { + type: 'array', + value: expect.any(Array), + }, 'device.processor_count': { type: 'integer', value: expect.any(Number), From f76bca29fce392749b267d7238718f2adeb4d0a1 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 17:32:46 +0200 Subject: [PATCH 16/18] . --- packages/core/src/attributes.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index a93968addc69..76aa99693fdd 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -8,14 +8,14 @@ export type RawAttribute = T extends { value: any } | { unit: any } ? Attribu export type Attributes = Record; -export type AttributeValueType = string | number | boolean | Array; +export type AttributeValueType = string | number | boolean | Array | Array | Array; type AttributeTypeMap = { string: string; integer: number; double: number; boolean: boolean; - array: Array; + array: Array | Array | Array; }; /* Generates a type from the AttributeTypeMap like: @@ -143,14 +143,10 @@ export function estimateTypedAttributesSizeInBytes(attributes: Attributes | unde weight += (attr.unit?.length ?? 0) * 2; const val = attr.value; - if (Array.isArray(val) && isPrimitive(val[0])) { + if (Array.isArray(val)) { // Assumption: Individual array items have the same type and roughly the same size // probably not always true but allows us to cut down on runtime weight += estimatePrimitiveSizeInBytes(val[0]) * val.length; - } else if (Array.isArray(val) && !isPrimitive(val[0])) { - // Fallback for arrays with non-primitive values (e.g. objects) - // Again (to cut down on runtime), we intentionally don't traverse the full hierarchy - weight += fallbackWeight * val.length; } else if (isPrimitive(val)) { weight += estimatePrimitiveSizeInBytes(val); } else { From 1538b0df6eeb8d3892494c60a6922e248df98f12 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 17:34:01 +0200 Subject: [PATCH 17/18] . --- packages/core/src/attributes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index 76aa99693fdd..ed1de134ce64 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -135,7 +135,6 @@ export function estimateTypedAttributesSizeInBytes(attributes: Attributes | unde return 0; } let weight = 0; - const fallbackWeight = 100; for (const [key, attr] of Object.entries(attributes)) { weight += key.length * 2; @@ -151,7 +150,7 @@ export function estimateTypedAttributesSizeInBytes(attributes: Attributes | unde weight += estimatePrimitiveSizeInBytes(val); } else { // default fallback for anything else (objects) - weight += fallbackWeight; + weight += 100; } } return weight; From f24852d2623f88b189eb5a304274fe82208ad6d3 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 4 May 2026 17:35:04 +0200 Subject: [PATCH 18/18] . --- packages/core/src/attributes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/attributes.ts b/packages/core/src/attributes.ts index ed1de134ce64..9473b01df87e 100644 --- a/packages/core/src/attributes.ts +++ b/packages/core/src/attributes.ts @@ -135,7 +135,6 @@ export function estimateTypedAttributesSizeInBytes(attributes: Attributes | unde return 0; } let weight = 0; - for (const [key, attr] of Object.entries(attributes)) { weight += key.length * 2; weight += attr.type.length * 2;