Skip to content
10 changes: 5 additions & 5 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)',
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
},
Expand All @@ -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' },
},
Expand All @@ -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' },
Expand All @@ -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' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
Expand Down Expand Up @@ -199,6 +200,10 @@ sentryTest(
type: 'string',
value: SDK_VERSION,
},
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['SpanStreaming']),
},
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
type: 'string',
value: segmentSpanId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
Expand Down Expand Up @@ -72,6 +73,10 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser
type: 'string',
value: SDK_VERSION,
},
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']),
},
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
type: 'string',
value: interactionSegmentSpan!.span_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
} from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
Expand Down Expand Up @@ -123,6 +124,10 @@ sentryTest('starts a streamed navigation span on page navigation', async ({ brow
type: 'string',
value: SDK_VERSION,
},
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']),
},
'sentry.segment.id': {
type: 'string',
value: navigationSpan.span_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
Expand Down Expand Up @@ -130,6 +131,10 @@ sentryTest(
type: 'string',
value: SDK_VERSION,
},
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']),
},
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
type: 'string',
value: pageloadSpan?.span_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import type { Envelope, SerializedStreamedSpanContainer } from '@sentry/core';
import {
SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
} from '@sentry/core';
import { expect, it } from 'vitest';
Expand Down Expand Up @@ -175,6 +176,10 @@ it('sends a streamed span envelope with correct spans for a manually started spa
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: { type: 'string', value: CLOUDFLARE_SDK },
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: { type: 'string', value: SDK_VERSION },
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['SpanStreaming']),
},
[SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: { type: 'string', value: '1.0.0' },
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.cloudflare' },
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: { type: 'string', value: segmentSpanId },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ const SEGMENT_SPAN = {
type: 'string',
value: expect.any(String),
},
'sentry.sdk.integrations': {
type: 'array',
value: expect.arrayContaining(['SpanStreaming']),
},
'sentry.segment.id': {
type: 'string',
value: expect.stringMatching(/^[\da-f]{16}$/),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ async function run(): Promise<void> {

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
},
Expand All @@ -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' },
},
Expand All @@ -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' },
Expand All @@ -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' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
Expand Down Expand Up @@ -129,6 +130,10 @@ test('sends a streamed span envelope with correct spans for a manually started s
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 },
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: { type: 'string', value: 'sentry.javascript.node' },
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: { type: 'string', value: SDK_VERSION },
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: expect.arrayContaining(['SpanStreaming']),
},
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: { type: 'string', value: segmentSpanId },
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: { type: 'string', value: 'test-span' },
[SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: { type: 'string', value: '1.0.0' },
Expand Down
6 changes: 5 additions & 1 deletion packages/browser/test/integrations/spanstreaming.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as SentryCore from '@sentry/core';
import { debug } from '@sentry/core';
import { debug, SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS } from '@sentry/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BrowserClient, spanStreamingIntegration } from '../../src';
import { getDefaultBrowserClientOptions } from '../helper/browser-client-options';
Expand Down Expand Up @@ -145,6 +145,10 @@ describe('spanStreamingIntegration', () => {
type: 'string',
value: expect.any(String),
},
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
type: 'array',
value: ['SpanStreaming'],
},
'sentry.segment.id': {
type: 'string',
value: span.spanContext().spanId,
Expand Down
33 changes: 19 additions & 14 deletions packages/core/src/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ type AttributeTypeMap = {
integer: number;
double: number;
boolean: boolean;
'string[]': Array<string>;
'integer[]': Array<number>;
'double[]': Array<number>;
'boolean[]': Array<boolean>;
array: Array<string> | Array<number> | Array<boolean>;
};

/* Generates a type from the AttributeTypeMap like:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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'
Expand All @@ -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);
}
2 changes: 2 additions & 0 deletions packages/core/src/semanticAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export const SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID = 'sentry.segment.id';
export const SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME = 'sentry.sdk.name';
/** The version of the Sentry SDK */
export const SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION = 'sentry.sdk.version';
/** The list of integrations enabled in the Sentry SDK (e.g., ["InboundFilters", "BrowserTracing"]) */
export const SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS = 'sentry.sdk.integrations';

/** The user ID (gated by sendDefaultPii) */
export const SEMANTIC_ATTRIBUTE_USER_ID = 'user.id';
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/tracing/spans/captureSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ScopeData } from '../../scope';
import {
SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
Expand Down Expand Up @@ -53,6 +54,7 @@ export function captureSpan(span: Span, client: Client): SerializedStreamedSpanW

if (spanJSON.is_segment) {
applyScopeToSegmentSpan(spanJSON, finalScopeData);
applySdkMetadataToSegmentSpan(spanJSON, client);
// Allow hook subscribers to mutate the segment span JSON
// This also invokes the `processSegmentSpan` hook of all integrations
client.emit('processSegmentSpan', spanJSON);
Expand Down Expand Up @@ -90,6 +92,15 @@ function applyScopeToSegmentSpan(_segmentSpanJSON: StreamedSpanJSON, _scopeData:
// This will follow in a separate PR
}

function applySdkMetadataToSegmentSpan(segmentSpanJSON: StreamedSpanJSON, client: Client): void {
const integrationNames = client.getOptions().integrations.map(i => i.name);
if (!integrationNames.length) return;

safeSetSpanJSONAttributes(segmentSpanJSON, {
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: integrationNames,
});
}

function applyCommonSpanAttributes(
spanJSON: StreamedSpanJSON,
serializedSegmentSpan: StreamedSpanJSON,
Expand Down
Loading
Loading