Skip to content

Commit 16eab59

Browse files
authored
feat(core): Support array attributes for spans, logs and metrics (#20427)
Adds array attribute support for spans, logs, and metrics. Relay only supports primitive homogeneous arrays (all elements have the same primitive type). However, any attributes with non-conforming arrays will be dropped by Relay, so there is no need for runtime validation on the SDK end. This change as is comes with a behavioral change for user-defined logs and metrics. If users previously set array attributes for logs/metrics then these were previously sent to Sentry stringified, now these will be sent as arrays. After discussion with product teams we also decided not to drop empty arrays. Closes #20651 Closes #20438
1 parent 53c98be commit 16eab59

15 files changed

Lines changed: 87 additions & 89 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
- **feat(core): Support array attributes for spans, logs, and metrics ([#20427](https://github.com/getsentry/sentry-javascript/pull/20427))**
8+
9+
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.
10+
711
## 10.53.1
812

913
- fix(core): Don't gate user data for streamed spans at scope read time ([#20827](https://github.com/getsentry/sentry-javascript/pull/20827))

dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
162162
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
163163
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
164164
'sentry.message.template': { value: 'Array: {}', type: 'string' },
165-
'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' },
165+
'sentry.message.parameter.0': { value: [1, 2, 3, 'string'], type: 'array' },
166166
},
167167
},
168168
{
@@ -179,7 +179,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
179179
'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' },
180180
'sentry.message.parameter.0': { value: 'prefix', type: 'string' },
181181
'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' },
182-
'sentry.message.parameter.2': { value: '[4,5,6]', type: 'string' },
182+
'sentry.message.parameter.2': { value: [4, 5, 6], type: 'array' },
183183
'sentry.message.parameter.3': { value: 'suffix', type: 'string' },
184184
},
185185
},

dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/subject.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Sentry.logger.info('log_before_any_scope', { log_attr: 'log_attr_1' });
33

44
Sentry.getGlobalScope().setAttributes({ global_scope_attr: true });
55

6-
// this attribute will not be sent for now
76
Sentry.getGlobalScope().setAttribute('array_attr', [1, 2, 3]);
87

98
// global scope, log attribute

dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page
4949
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
5050
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
5151
global_scope_attr: { value: true, type: 'boolean' },
52+
array_attr: { value: [1, 2, 3], type: 'array' },
5253
log_attr: { value: 'log_attr_2', type: 'string' },
5354
},
5455
},
@@ -63,6 +64,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page
6364
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
6465
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
6566
global_scope_attr: { value: true, type: 'boolean' },
67+
array_attr: { value: [1, 2, 3], type: 'array' },
6668
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
6769
log_attr: { value: 'log_attr_3', type: 'string' },
6870
},
@@ -78,6 +80,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page
7880
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
7981
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
8082
global_scope_attr: { value: true, type: 'boolean' },
83+
array_attr: { value: [1, 2, 3], type: 'array' },
8184
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
8285
scope_attr: { value: 200, unit: 'millisecond', type: 'integer' },
8386
log_attr: { value: 'log_attr_4', type: 'string' },
@@ -94,6 +97,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page
9497
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
9598
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
9699
global_scope_attr: { value: true, type: 'boolean' },
100+
array_attr: { value: [1, 2, 3], type: 'array' },
97101
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
98102
scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' },
99103
log_attr: { value: 'log_attr_5', type: 'string' },

dev-packages/e2e-tests/test-applications/deno-streamed/tests/spans.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ const SEGMENT_SPAN = {
1515
type: 'integer',
1616
value: expect.any(Number),
1717
},
18-
// TODO: 'device.archs' is set but arrays are not yet serialized in span attributes
18+
'device.archs': {
19+
type: 'array',
20+
value: expect.any(Array),
21+
},
1922
'device.processor_count': {
2023
type: 'integer',
2124
value: expect.any(Number),

dev-packages/node-core-integration-tests/suites/public-api/startSpan/basic-usage-streamed/test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ test('sends a streamed span envelope with correct spans for a manually started s
143143
'device.processor_frequency': { type: 'integer', value: expect.any(Number) },
144144
'culture.locale': { type: 'string', value: expect.any(String) },
145145
'culture.timezone': { type: 'string', value: expect.any(String) },
146-
// TODO: device.archs is an array and currently dropped during serialization
147-
// 'device.archs': { type: 'array', value: [expect.any(String)] },
146+
'device.archs': { type: 'array', value: expect.any(Array) },
148147
};
149148

150149
// process.availableMemory is only available in Node 22+

dev-packages/node-integration-tests/suites/context-streamed/test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ test('nodeContextIntegration sets context attributes on segment spans', async ()
1616

1717
// Static attributes
1818
expect(attrs['app.start_time']).toEqual({ type: 'string', value: expect.any(String) });
19-
// TODO: device.archs is an array and currently dropped during serialization
20-
// expect(attrs['device.archs']).toEqual({ type: 'array', value: [expect.any(String)] });
19+
expect(attrs['device.archs']).toEqual({ type: 'array', value: expect.any(Array) });
2120
expect(attrs['device.boot_time']).toEqual({ type: 'string', value: expect.any(String) });
2221
expect(attrs['device.processor_count']).toEqual({ type: 'integer', value: expect.any(Number) });
2322
expect(attrs['device.cpu_description']).toEqual({ type: 'string', value: expect.any(String) });

dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ async function run(): Promise<void> {
1515

1616
Sentry.getGlobalScope().setAttribute('global_scope_attr', true);
1717

18-
// this attribute will not be sent for now
1918
Sentry.getGlobalScope().setAttributes({ array_attr: [1, 2, 3] });
2019

2120
// global scope, log attribute

dev-packages/node-integration-tests/suites/public-api/logger/test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('logs', () => {
6161
attributes: {
6262
...commonAttributes,
6363
global_scope_attr: { value: true, type: 'boolean' },
64+
array_attr: { value: [1, 2, 3], type: 'array' },
6465
log_attr: { value: 'log_attr_2', type: 'string' },
6566
},
6667
},
@@ -73,6 +74,7 @@ describe('logs', () => {
7374
attributes: {
7475
...commonAttributes,
7576
global_scope_attr: { value: true, type: 'boolean' },
77+
array_attr: { value: [1, 2, 3], type: 'array' },
7678
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
7779
log_attr: { value: 'log_attr_3', type: 'string' },
7880
},
@@ -86,6 +88,7 @@ describe('logs', () => {
8688
attributes: {
8789
...commonAttributes,
8890
global_scope_attr: { value: true, type: 'boolean' },
91+
array_attr: { value: [1, 2, 3], type: 'array' },
8992
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
9093
scope_attr: { value: 200, unit: 'millisecond', type: 'integer' },
9194
log_attr: { value: 'log_attr_4', type: 'string' },
@@ -100,6 +103,7 @@ describe('logs', () => {
100103
attributes: {
101104
...commonAttributes,
102105
global_scope_attr: { value: true, type: 'boolean' },
106+
array_attr: { value: [1, 2, 3], type: 'array' },
103107
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
104108
scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' },
105109
log_attr: { value: 'log_attr_5', type: 'string' },

dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,7 @@ test('sends a streamed span envelope with correct spans for a manually started s
135135
'process.runtime.engine.version': { type: 'string', value: expect.any(String) },
136136
'app.start_time': { type: 'string', value: expect.any(String) },
137137
'app.memory': { type: 'integer', value: expect.any(Number) },
138-
// TODO: device.archs is an array and currently dropped during serialization
139-
// 'device.archs': { type: 'array', value: [expect.any(String)] },
138+
'device.archs': { type: 'array', value: expect.any(Array) },
140139
'device.boot_time': { type: 'string', value: expect.any(String) },
141140
'device.memory_size': { type: 'integer', value: expect.any(Number) },
142141
'device.free_memory': { type: 'integer', value: expect.any(Number) },

0 commit comments

Comments
 (0)