Skip to content

Commit c729b6e

Browse files
committed
create span v2 envelope
1 parent c911ecb commit c729b6e

File tree

5 files changed

+64
-27
lines changed

5 files changed

+64
-27
lines changed

packages/browser/src/integrations/spanstreaming.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import type { Envelope, IntegrationFn, Span, SpanV2JSON } from '@sentry/core';
2-
import { createEnvelope, debug, defineIntegration, isV2BeforeSendSpanCallback, spanToV2JSON } from '@sentry/core';
1+
import type { IntegrationFn, Span, SpanV2JSON } from '@sentry/core';
2+
import {
3+
debug,
4+
defineIntegration,
5+
getDynamicSamplingContextFromSpan,
6+
isV2BeforeSendSpanCallback,
7+
spanToV2JSON,
8+
} from '@sentry/core';
39
import { DEBUG_BUILD } from '../debug-build';
10+
import { createSpanV2Envelope } from '@sentry/core/build/types/envelope';
411

512
export interface SpanStreamingOptions {
613
batchLimit: number;
@@ -35,13 +42,14 @@ const _spanStreamingIntegration = ((userOptions?: Partial<SpanStreamingOptions>)
3542
const initialMessage = 'spanStreamingIntegration requires';
3643
const fallbackMsg = 'Falling back to static trace lifecycle.';
3744

38-
if (DEBUG_BUILD && clientOptions.traceLifecycle !== 'streamed') {
39-
debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "streamed"! ${fallbackMsg}`);
45+
if (clientOptions.traceLifecycle !== 'streamed') {
46+
DEBUG_BUILD && debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "streamed"! ${fallbackMsg}`);
4047
return;
4148
}
4249

43-
if (DEBUG_BUILD && beforeSendSpan && !isV2BeforeSendSpanCallback(beforeSendSpan)) {
44-
debug.warn(`${initialMessage} a beforeSendSpan callback using \`makeV2Callback\`! ${fallbackMsg}`);
50+
if (beforeSendSpan && !isV2BeforeSendSpanCallback(beforeSendSpan)) {
51+
DEBUG_BUILD &&
52+
debug.warn(`${initialMessage} a beforeSendSpan callback using \`makeV2Callback\`! ${fallbackMsg}`);
4553
return;
4654
}
4755

@@ -54,6 +62,8 @@ const _spanStreamingIntegration = ((userOptions?: Partial<SpanStreamingOptions>)
5462
}
5563
});
5664

65+
// For now, we send all spans on local segment (root) span end.
66+
// TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
5767
client.on('segmentSpanEnd', segmentSpan => {
5868
const traceId = segmentSpan.spanContext().traceId;
5969
const spansOfTrace = traceMap.get(traceId);
@@ -65,25 +75,24 @@ const _spanStreamingIntegration = ((userOptions?: Partial<SpanStreamingOptions>)
6575

6676
const serializedSpans = Array.from(spansOfTrace ?? []).map(span => {
6777
const serializedSpan = spanToV2JSON(span);
68-
const finalSpan = beforeSendSpan ? beforeSendSpan(serializedSpan) : serializedSpan;
69-
return finalSpan;
78+
return beforeSendSpan ? beforeSendSpan(serializedSpan) : serializedSpan;
7079
});
7180

7281
const batches: SpanV2JSON[][] = [];
7382
for (let i = 0; i < serializedSpans.length; i += options.batchLimit) {
7483
batches.push(serializedSpans.slice(i, i + options.batchLimit));
7584
}
7685

77-
debug.log(`Sending trace ${traceId} in ${batches.length} batche${batches.length === 1 ? '' : 's'}`);
86+
DEBUG_BUILD &&
87+
debug.log(`Sending trace ${traceId} in ${batches.length} batche${batches.length === 1 ? '' : 's'}`);
7888

7989
// TODO: Apply scopes to spans
80-
81-
// TODO: Apply beforeSendSpan to spans
82-
8390
// TODO: Apply ignoreSpans to spans
8491

92+
const dsc = getDynamicSamplingContextFromSpan(segmentSpan);
93+
8594
for (const batch of batches) {
86-
const envelope = createSpanStreamEnvelope(batch);
95+
const envelope = createSpanV2Envelope(batch, dsc, client);
8796
// no need to handle client reports for network errors,
8897
// buffer overflows or rate limiting here. All of this is handled
8998
// by client and transport.
@@ -99,7 +108,3 @@ const _spanStreamingIntegration = ((userOptions?: Partial<SpanStreamingOptions>)
99108
}) satisfies IntegrationFn;
100109

101110
export const spanStreamingIntegration = defineIntegration(_spanStreamingIntegration);
102-
103-
function createSpanStreamEnvelope(serializedSpans: StreamedSpanJSON[]): Envelope {
104-
return createEnvelope<SpanEnvelope>(headers, [item]);
105-
}

packages/core/src/envelope.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import type {
1111
RawSecurityItem,
1212
SessionEnvelope,
1313
SessionItem,
14+
SpanContainerItem,
1415
SpanEnvelope,
1516
SpanItem,
17+
SpanV2Envelope,
1618
} from './types-hoist/envelope';
1719
import type { Event } from './types-hoist/event';
1820
import type { SdkInfo } from './types-hoist/sdkinfo';
1921
import type { SdkMetadata } from './types-hoist/sdkmetadata';
2022
import type { Session, SessionAggregates } from './types-hoist/session';
23+
import { SpanV2JSON } from './types-hoist/span';
2124
import { isV2BeforeSendSpanCallback } from './utils/beforeSendSpan';
2225
import { dsnToString } from './utils/dsn';
2326
import {
@@ -121,10 +124,6 @@ export function createEventEnvelope(
121124
* Takes an optional client and runs spans through `beforeSendSpan` if available.
122125
*/
123126
export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?: Client): SpanEnvelope {
124-
function dscHasRequiredProps(dsc: Partial<DynamicSamplingContext>): dsc is DynamicSamplingContext {
125-
return !!dsc.trace_id && !!dsc.public_key;
126-
}
127-
128127
// For the moment we'll obtain the DSC from the first span in the array
129128
// This might need to be changed if we permit sending multiple spans from
130129
// different segments in one envelope
@@ -180,6 +179,33 @@ export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?
180179
return createEnvelope<SpanEnvelope>(headers, items);
181180
}
182181

182+
/**
183+
* Creates a span v2 envelope
184+
*/
185+
export function createSpanV2Envelope(
186+
serializedSpans: SpanV2JSON[],
187+
dsc: Partial<DynamicSamplingContext>,
188+
client: Client,
189+
): SpanV2Envelope {
190+
const dsn = client?.getDsn();
191+
const tunnel = client?.getOptions().tunnel;
192+
const sdk = client?.getOptions()._metadata?.sdk;
193+
194+
const headers: SpanV2Envelope[0] = {
195+
sent_at: new Date().toISOString(),
196+
...(dscHasRequiredProps(dsc) && { trace: dsc }),
197+
...(sdk && { sdk: sdk }),
198+
...(!!tunnel && dsn && { dsn: dsnToString(dsn) }),
199+
};
200+
201+
const spanContainer: SpanContainerItem = [
202+
{ type: 'span', item_count: serializedSpans.length, content_type: 'application/vnd.sentry.items.span.v2+json' },
203+
{ items: serializedSpans },
204+
];
205+
206+
return createEnvelope<SpanV2Envelope>(headers, [spanContainer]);
207+
}
208+
183209
/**
184210
* Create an Envelope from a CSP report.
185211
*/
@@ -202,3 +228,7 @@ export function createRawSecurityEnvelope(
202228

203229
return createEnvelope<RawSecurityEnvelope>(envelopeHeaders, [eventItem]);
204230
}
231+
232+
function dscHasRequiredProps(dsc: Partial<DynamicSamplingContext>): dsc is DynamicSamplingContext {
233+
return !!dsc.trace_id && !!dsc.public_key;
234+
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ export type {
355355
ProfileChunkEnvelope,
356356
ProfileChunkItem,
357357
SpanEnvelope,
358+
SpanV2Envelope,
358359
SpanItem,
359360
LogEnvelope,
360361
} from './types-hoist/envelope';

packages/core/src/types-hoist/envelope.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ type CheckInItemHeaders = { type: 'check_in' };
8888
type ProfileItemHeaders = { type: 'profile' };
8989
type ProfileChunkItemHeaders = { type: 'profile_chunk' };
9090
type SpanItemHeaders = { type: 'span' };
91-
type SpanV2ItemHeaders = {
91+
type SpanContainerItemHeaders = {
9292
/**
93-
* Same as v1 span item type but this envelope is distinguished by {@link SpanV2ItemHeaders.content_type}.
93+
* Same as v1 span item type but this envelope is distinguished by {@link SpanContainerItemHeaders.content_type}.
9494
*/
9595
type: 'span';
9696
/**
@@ -130,7 +130,7 @@ export type FeedbackItem = BaseEnvelopeItem<FeedbackItemHeaders, FeedbackEvent>;
130130
export type ProfileItem = BaseEnvelopeItem<ProfileItemHeaders, Profile>;
131131
export type ProfileChunkItem = BaseEnvelopeItem<ProfileChunkItemHeaders, ProfileChunk>;
132132
export type SpanItem = BaseEnvelopeItem<SpanItemHeaders, Partial<SpanJSON>>;
133-
export type SpanV2Item = BaseEnvelopeItem<SpanV2ItemHeaders, SerializedSpanContainer>;
133+
export type SpanContainerItem = BaseEnvelopeItem<SpanContainerItemHeaders, SerializedSpanContainer>;
134134
export type LogContainerItem = BaseEnvelopeItem<LogContainerItemHeaders, SerializedLogContainer>;
135135
export type RawSecurityItem = BaseEnvelopeItem<RawSecurityHeaders, LegacyCSPReport>;
136136

@@ -140,7 +140,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext };
140140
type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders;
141141
type ReplayEnvelopeHeaders = BaseEnvelopeHeaders;
142142
type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
143-
type SpanV2EnvelopeHeaders = BaseEnvelopeHeaders & { trace: DynamicSamplingContext };
143+
type SpanV2EnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
144144
type LogEnvelopeHeaders = BaseEnvelopeHeaders;
145145
export type EventEnvelope = BaseEnvelope<
146146
EventEnvelopeHeaders,
@@ -151,7 +151,7 @@ export type ClientReportEnvelope = BaseEnvelope<ClientReportEnvelopeHeaders, Cli
151151
export type ReplayEnvelope = [ReplayEnvelopeHeaders, [ReplayEventItem, ReplayRecordingItem]];
152152
export type CheckInEnvelope = BaseEnvelope<CheckInEnvelopeHeaders, CheckInItem>;
153153
export type SpanEnvelope = BaseEnvelope<SpanEnvelopeHeaders, SpanItem>;
154-
export type SpanV2Envelope = BaseEnvelope<SpanV2EnvelopeHeaders, SpanV2Item>;
154+
export type SpanV2Envelope = BaseEnvelope<SpanV2EnvelopeHeaders, SpanContainerItem>;
155155
export type ProfileChunkEnvelope = BaseEnvelope<BaseEnvelopeHeaders, ProfileChunkItem>;
156156
export type RawSecurityEnvelope = BaseEnvelope<BaseEnvelopeHeaders, RawSecurityItem>;
157157
export type LogEnvelope = BaseEnvelope<LogEnvelopeHeaders, LogContainerItem>;
@@ -164,6 +164,7 @@ export type Envelope =
164164
| ReplayEnvelope
165165
| CheckInEnvelope
166166
| SpanEnvelope
167+
| SpanV2Envelope
167168
| RawSecurityEnvelope
168169
| LogEnvelope;
169170
export type EnvelopeItem = Envelope[1][number];

packages/core/src/types-hoist/span.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SerializedAttributes } from './attributes';
1+
import type { SerializedAttributes } from './attributes';
22
import type { SpanLink, SpanLinkJSON } from './link';
33
import type { Measurements } from './measurement';
44
import type { HrTime } from './opentelemetry';

0 commit comments

Comments
 (0)