1- import type { Client , IntegrationFn , Span , SpanAttributes , SpanAttributeValue , SpanV2JSON } from '@sentry/core' ;
1+ import type { Client , IntegrationFn , Scope , ScopeData , Span , SpanAttributes , SpanV2JSON } from '@sentry/core' ;
22import {
3+ attributesFromObject ,
34 createSpanV2Envelope ,
45 debug ,
56 defineIntegration ,
67 getCapturedScopesOnSpan ,
78 getDynamicSamplingContextFromSpan ,
89 getGlobalScope ,
910 getRootSpan as getSegmentSpan ,
11+ httpHeadersToSpanAttributes ,
1012 isV2BeforeSendSpanCallback ,
1113 mergeScopeData ,
1214 reparentChildSpans ,
15+ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ,
1316 SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT ,
1417 SEMANTIC_ATTRIBUTE_SENTRY_RELEASE ,
1518 SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME ,
1619 SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION ,
1720 SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME ,
21+ SEMANTIC_ATTRIBUTE_URL_FULL ,
1822 SEMANTIC_ATTRIBUTE_USER_EMAIL ,
1923 SEMANTIC_ATTRIBUTE_USER_ID ,
2024 SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS ,
@@ -24,6 +28,7 @@ import {
2428 spanToV2JSON ,
2529} from '@sentry/core' ;
2630import { DEBUG_BUILD } from '../debug-build' ;
31+ import { getHttpRequestData } from '../helpers' ;
2732
2833export interface SpanStreamingOptions {
2934 batchLimit : number ;
@@ -40,11 +45,11 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
4045 }
4146
4247 const options : SpanStreamingOptions = {
48+ ...userOptions ,
4349 batchLimit :
4450 userOptions ?. batchLimit && userOptions . batchLimit <= 1000 && userOptions . batchLimit >= 1
4551 ? userOptions . batchLimit
4652 : 1000 ,
47- ...userOptions ,
4853 } ;
4954
5055 // key: traceId-segmentSpanId
@@ -59,14 +64,14 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
5964 const initialMessage = 'spanStreamingIntegration requires' ;
6065 const fallbackMsg = 'Falling back to static trace lifecycle.' ;
6166
62- if ( clientOptions . traceLifecycle !== 'streamed ' ) {
63- DEBUG_BUILD && debug . warn ( `${ initialMessage } \`traceLifecycle\` to be set to "streamed "! ${ fallbackMsg } ` ) ;
67+ if ( clientOptions . traceLifecycle !== 'stream ' ) {
68+ DEBUG_BUILD && debug . warn ( `${ initialMessage } \`traceLifecycle\` to be set to "stream "! ${ fallbackMsg } ` ) ;
6469 return ;
6570 }
6671
6772 if ( beforeSendSpan && ! isV2BeforeSendSpanCallback ( beforeSendSpan ) ) {
68- DEBUG_BUILD &&
69- debug . warn ( `${ initialMessage } a beforeSendSpan callback using \`makeV2Callback\`! ${ fallbackMsg } ` ) ;
73+ client . getOptions ( ) . traceLifecycle = 'static' ;
74+ debug . warn ( `${ initialMessage } a beforeSendSpan callback using \`makeV2Callback\`! ${ fallbackMsg } ` ) ;
7075 return ;
7176 }
7277
@@ -82,16 +87,14 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
8287
8388 // For now, we send all spans on local segment (root) span end.
8489 // TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
85- client . on (
86- 'segmentSpanEnd' ,
87- segmentSpan => ( ) =>
88- processAndSendSpans ( segmentSpan , {
89- spanTreeMap : spanTreeMap ,
90- client,
91- batchLimit : options . batchLimit ,
92- beforeSendSpan,
93- } ) ,
94- ) ;
90+ client . on ( 'segmentSpanEnd' , segmentSpan => {
91+ processAndSendSpans ( segmentSpan , {
92+ spanTreeMap : spanTreeMap ,
93+ client,
94+ batchLimit : options . batchLimit ,
95+ beforeSendSpan,
96+ } ) ;
97+ } ) ;
9598 } ,
9699 } ;
97100} ) satisfies IntegrationFn ) ;
@@ -122,12 +125,15 @@ function processAndSendSpans(
122125 spanTreeMap . delete ( spanTreeMapKey ) ;
123126 return ;
124127 }
128+
125129 const segmentSpanJson = spanToV2JSON ( segmentSpan ) ;
126130
127131 for ( const span of spansOfTrace ) {
128132 applyCommonSpanAttributes ( span , segmentSpanJson , client ) ;
129133 }
130134
135+ applyScopeToSegmentSpan ( segmentSpan , segmentSpanJson , client ) ;
136+
131137 // TODO: Apply scope data and contexts to segment span
132138
133139 const { ignoreSpans } = client . getOptions ( ) ;
@@ -139,7 +145,12 @@ function processAndSendSpans(
139145 return ;
140146 }
141147
142- const serializedSpans = Array . from ( spansOfTrace ?? [ ] ) . map ( spanToV2JSON ) ;
148+ const serializedSpans = Array . from ( spansOfTrace ?? [ ] ) . map ( s => {
149+ const serialized = spanToV2JSON ( s ) ;
150+ // remove internal span attributes we don't need to send.
151+ delete serialized . attributes ?. [ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ] ;
152+ return serialized ;
153+ } ) ;
143154
144155 const processedSpans = [ ] ;
145156 let ignoredSpanCount = 0 ;
@@ -168,7 +179,7 @@ function processAndSendSpans(
168179 batches . push ( processedSpans . slice ( i , i + batchLimit ) ) ;
169180 }
170181
171- DEBUG_BUILD && debug . log ( `Sending trace ${ traceId } in ${ batches . length } batche ${ batches . length === 1 ? '' : 's ' } ` ) ;
182+ DEBUG_BUILD && debug . log ( `Sending trace ${ traceId } in ${ batches . length } batch ${ batches . length === 1 ? '' : 'es ' } ` ) ;
172183
173184 const dsc = getDynamicSamplingContextFromSpan ( segmentSpan ) ;
174185
@@ -193,14 +204,7 @@ function applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON
193204
194205 const originalAttributeKeys = Object . keys ( spanToV2JSON ( span ) . attributes ?? { } ) ;
195206
196- // TODO: Extract this scope data merge to a helper in core. It's used in multiple places.
197- const finalScopeData = getGlobalScope ( ) . getScopeData ( ) ;
198- if ( spanIsolationScope ) {
199- mergeScopeData ( finalScopeData , spanIsolationScope . getScopeData ( ) ) ;
200- }
201- if ( spanScope ) {
202- mergeScopeData ( finalScopeData , spanScope . getScopeData ( ) ) ;
203- }
207+ const finalScopeData = getFinalScopeData ( spanIsolationScope , spanScope ) ;
204208
205209 // avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
206210 setAttributesIfNotPresent ( span , originalAttributeKeys , {
@@ -220,6 +224,35 @@ function applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON
220224 } ) ;
221225}
222226
227+ /**
228+ * Adds span attributes frome
229+ */
230+ function applyScopeToSegmentSpan ( segmentSpan : Span , serializedSegmentSpan : SpanV2JSON , client : Client ) : void {
231+ const { isolationScope, scope } = getCapturedScopesOnSpan ( segmentSpan ) ;
232+ const finalScopeData = getFinalScopeData ( isolationScope , scope ) ;
233+
234+ const browserRequestData = getHttpRequestData ( ) ;
235+
236+ const tags = finalScopeData . tags ?? { } ;
237+
238+ let contextAttributes = { } ;
239+ Object . keys ( finalScopeData . contexts ) . forEach ( key => {
240+ if ( finalScopeData . contexts [ key ] ) {
241+ contextAttributes = { ...contextAttributes , ...attributesFromObject ( finalScopeData . contexts [ key ] ) } ;
242+ }
243+ } ) ;
244+
245+ const extraAttributes = attributesFromObject ( finalScopeData . extra ) ;
246+
247+ setAttributesIfNotPresent ( segmentSpan , Object . keys ( serializedSegmentSpan . attributes ?? { } ) , {
248+ [ SEMANTIC_ATTRIBUTE_URL_FULL ] : browserRequestData . url ,
249+ ...httpHeadersToSpanAttributes ( browserRequestData . headers , client . getOptions ( ) . sendDefaultPii ?? false ) ,
250+ ...tags ,
251+ ...contextAttributes ,
252+ ...extraAttributes ,
253+ } ) ;
254+ }
255+
223256function applyBeforeSendSpanCallback ( span : SpanV2JSON , beforeSendSpan : ( span : SpanV2JSON ) => SpanV2JSON ) : SpanV2JSON {
224257 const modifedSpan = beforeSendSpan ( span ) ;
225258 if ( ! modifedSpan ) {
@@ -236,3 +269,15 @@ function setAttributesIfNotPresent(span: Span, originalAttributeKeys: string[],
236269 }
237270 } ) ;
238271}
272+
273+ // TODO: Extract this to a helper in core. It's used in multiple places.
274+ function getFinalScopeData ( isolationScope : Scope | undefined , scope : Scope | undefined ) : ScopeData {
275+ const finalScopeData = getGlobalScope ( ) . getScopeData ( ) ;
276+ if ( isolationScope ) {
277+ mergeScopeData ( finalScopeData , isolationScope . getScopeData ( ) ) ;
278+ }
279+ if ( scope ) {
280+ mergeScopeData ( finalScopeData , scope . getScopeData ( ) ) ;
281+ }
282+ return finalScopeData ;
283+ }
0 commit comments