Skip to content

Commit 1811e5d

Browse files
committed
add measurements to spanJSON
1 parent 9c41e52 commit 1811e5d

File tree

9 files changed

+104
-28
lines changed

9 files changed

+104
-28
lines changed

packages/core/src/envelope.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
SessionAggregates,
1212
SessionEnvelope,
1313
SessionItem,
14+
SpanEnvelope,
1415
} from '@sentry/types';
1516
import {
1617
createAttachmentEnvelopeItem,
@@ -19,6 +20,9 @@ import {
1920
dsnToString,
2021
getSdkMetadataForEnvelopeHeader,
2122
} from '@sentry/utils';
23+
import { createSpanEnvelopeItem } from '@sentry/utils';
24+
import type { SentrySpan } from './tracing';
25+
import { spanToJSON } from './utils/spanUtils';
2226

2327
/**
2428
* Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
@@ -117,3 +121,15 @@ export function createAttachmentEnvelope(
117121
}
118122
return createEnvelope<EventEnvelope>(envelopeHeaders, attachmentItems);
119123
}
124+
125+
/**
126+
* Create envelope from Span item.
127+
*/
128+
export function createSpanEnvelope(spans: SentrySpan[]): SpanEnvelope {
129+
const headers: SpanEnvelope[0] = {
130+
sent_at: new Date().toISOString(),
131+
};
132+
133+
const items = spans.map(span => createSpanEnvelopeItem(spanToJSON(span)));
134+
return createEnvelope<SpanEnvelope>(headers, items);
135+
}

packages/core/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type { IntegrationIndex } from './integration';
88

99
export * from './tracing';
1010
export * from './semanticAttributes';
11-
export { createEventEnvelope, createSessionEnvelope, createAttachmentEnvelope } from './envelope';
11+
export { createEventEnvelope, createSessionEnvelope, createAttachmentEnvelope, createSpanEnvelope } from './envelope';
1212
export {
1313
captureCheckIn,
1414
withMonitor,
@@ -61,7 +61,6 @@ export {
6161
export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent';
6262
export { prepareEvent } from './utils/prepareEvent';
6363
export { createCheckInEnvelope } from './checkin';
64-
export { createSpanEnvelope } from './span';
6564
export { hasTracingEnabled } from './utils/hasTracingEnabled';
6665
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
6766
export { handleCallbackErrors } from './utils/handleCallbackErrors';

packages/core/src/span.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/core/src/tracing/sentrySpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export class SentrySpan implements Span {
187187
_metrics_summary: getMetricSummaryJsonForSpan(this),
188188
profile_id: this._attributes[SEMANTIC_ATTRIBUTE_PROFILE_ID] as string | undefined,
189189
exclusive_time: this._attributes[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME] as number | undefined,
190+
measurements: timedEventsToMeasurements(this._events),
190191
});
191192
}
192193

@@ -297,7 +298,7 @@ export class SentrySpan implements Span {
297298
};
298299

299300
const measurements = timedEventsToMeasurements(this._events);
300-
const hasMeasurements = Object.keys(measurements).length;
301+
const hasMeasurements = measurements && Object.keys(measurements).length;
301302

302303
if (hasMeasurements) {
303304
DEBUG_BUILD &&

packages/opentelemetry/src/spanExporter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, spans: SpanJSON[], remai
281281
op,
282282
origin,
283283
_metrics_summary: getMetricSummaryJsonForSpan(span as unknown as Span),
284+
measurements: timedEventsToMeasurements(span.events),
284285
});
285286

286287
spans.push(spanJSON);

packages/types/src/envelope.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Profile } from './profiling';
88
import type { ReplayEvent, ReplayRecordingData } from './replay';
99
import type { SdkInfo } from './sdkinfo';
1010
import type { SerializedSession, Session, SessionAggregates } from './session';
11-
import type { Span } from './span';
11+
import type { SpanJSON } from './span';
1212

1313
// Based on: https://develop.sentry.dev/sdk/envelopes/
1414

@@ -98,7 +98,7 @@ type ReplayRecordingItem = BaseEnvelopeItem<ReplayRecordingItemHeaders, ReplayRe
9898
export type StatsdItem = BaseEnvelopeItem<StatsdItemHeaders, string>;
9999
export type FeedbackItem = BaseEnvelopeItem<FeedbackItemHeaders, FeedbackEvent>;
100100
export type ProfileItem = BaseEnvelopeItem<ProfileItemHeaders, Profile>;
101-
export type SpanItem = BaseEnvelopeItem<SpanItemHeaders, Span>;
101+
export type SpanItem = BaseEnvelopeItem<SpanItemHeaders, Partial<SpanJSON>>;
102102

103103
export type EventEnvelopeHeaders = { event_id: string; sent_at: string; trace?: DynamicSamplingContext };
104104
type SessionEnvelopeHeaders = { sent_at: string };

packages/types/src/span.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Measurements } from './measurement';
12
import type { Primitive } from './misc';
23
import type { HrTime } from './opentelemetry';
34
import type { SpanStatus } from './spanStatus';
@@ -56,6 +57,7 @@ export interface SpanJSON {
5657
_metrics_summary?: Record<string, Array<MetricSummary>>;
5758
profile_id?: string;
5859
exclusive_time?: number;
60+
measurements: Measurements;
5961
}
6062

6163
// These are aligned with OpenTelemetry trace flags

packages/utils/src/envelope.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import type {
1111
EventEnvelopeHeaders,
1212
SdkInfo,
1313
SdkMetadata,
14+
SpanItem,
15+
SpanJSON,
1416
} from '@sentry/types';
1517

1618
import { dsnToString } from './dsn';
@@ -177,6 +179,17 @@ export function parseEnvelope(env: string | Uint8Array): Envelope {
177179
return [envelopeHeader, items];
178180
}
179181

182+
/**
183+
* Creates envelope item for a single span
184+
*/
185+
export function createSpanEnvelopeItem(spanJson: Partial<SpanJSON>): SpanItem {
186+
const spanHeaders: SpanItem[0] = {
187+
type: 'span',
188+
};
189+
190+
return [spanHeaders, spanJson];
191+
}
192+
180193
/**
181194
* Creates attachment envelope items
182195
*/

packages/utils/test/envelope.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,82 @@
1-
import type { Event, EventEnvelope } from '@sentry/types';
1+
import type { Event, EventEnvelope, SpanAttributes } from '@sentry/types';
22

3+
import {
4+
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,
5+
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT,
6+
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE,
7+
spanToJSON,
8+
} from '@sentry/core';
9+
import { SentrySpan } from '@sentry/core';
310
import {
411
addItemToEnvelope,
512
createEnvelope,
13+
createSpanEnvelopeItem,
614
forEachEnvelopeItem,
715
parseEnvelope,
816
serializeEnvelope,
917
} from '../src/envelope';
18+
import { dropUndefinedKeys } from '../src/object';
1019
import type { InternalGlobal } from '../src/worldwide';
1120
import { GLOBAL_OBJ } from '../src/worldwide';
1221

1322
describe('envelope', () => {
23+
describe('createSpanEnvelope()', () => {
24+
it('span-envelope-item of INP event has the correct object structure', () => {
25+
const attributes: SpanAttributes = dropUndefinedKeys({
26+
release: 'releaseString',
27+
environment: 'dev',
28+
transaction: '/test-route',
29+
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 80,
30+
user: 10,
31+
profile_id: 'test-profile-id',
32+
replay_id: 'test-replay-id',
33+
});
34+
35+
const startTime = 1713365480;
36+
37+
const span = new SentrySpan({
38+
startTimestamp: startTime,
39+
endTimestamp: startTime + 2,
40+
op: 'ui.interaction.click',
41+
name: '<unknown>',
42+
attributes,
43+
});
44+
45+
span.addEvent('inp', {
46+
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: 'millisecond',
47+
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: 100,
48+
});
49+
50+
const spanEnvelopeItem = createSpanEnvelopeItem(spanToJSON(span));
51+
52+
const expectedObj = {
53+
data: {
54+
'sentry.origin': expect.any(String),
55+
'sentry.op': expect.any(String),
56+
release: expect.any(String),
57+
environment: expect.any(String),
58+
transaction: expect.any(String),
59+
'sentry.exclusive_time': expect.any(Number),
60+
user: expect.any(Number),
61+
profile_id: expect.any(String),
62+
replay_id: expect.any(String),
63+
},
64+
description: expect.any(String),
65+
op: expect.any(String),
66+
span_id: expect.any(String),
67+
start_timestamp: expect.any(Number),
68+
timestamp: expect.any(Number),
69+
trace_id: expect.any(String),
70+
origin: expect.any(String),
71+
exclusive_time: expect.any(Number),
72+
measurements: { inp: { value: expect.any(Number), unit: expect.any(String) } },
73+
};
74+
75+
expect(spanEnvelopeItem[0].type).toBe('span');
76+
expect(spanEnvelopeItem[1]).toMatchObject(expectedObj);
77+
});
78+
});
79+
1480
describe('createEnvelope()', () => {
1581
const testTable: Array<[string, Parameters<typeof createEnvelope>[0], Parameters<typeof createEnvelope>[1]]> = [
1682
['creates an empty envelope', {}, []],

0 commit comments

Comments
 (0)