1- import type { Client , IntegrationFn , Scope , ScopeData , Span , SpanAttributes , SpanV2JSON } from '@sentry/core' ;
1+ import type { Client , IntegrationFn , Span , SpanV2JSON } from '@sentry/core' ;
22import {
3+ captureSpan ,
34 createSpanV2Envelope ,
45 debug ,
56 defineIntegration ,
6- getCapturedScopesOnSpan ,
77 getDynamicSamplingContextFromSpan ,
8- getGlobalScope ,
98 INTERNAL_getSegmentSpan ,
109 isV2BeforeSendSpanCallback ,
11- mergeScopeData ,
12- reparentChildSpans ,
13- SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ,
14- SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT ,
15- SEMANTIC_ATTRIBUTE_SENTRY_RELEASE ,
16- SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME ,
17- SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION ,
18- SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID ,
19- SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME ,
20- SEMANTIC_ATTRIBUTE_USER_EMAIL ,
21- SEMANTIC_ATTRIBUTE_USER_ID ,
22- SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS ,
23- SEMANTIC_ATTRIBUTE_USER_USERNAME ,
24- shouldIgnoreSpan ,
2510 showSpanDropWarning ,
2611 spanToV2JSON ,
2712} from '@sentry/core' ;
@@ -72,7 +57,7 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
7257 return ;
7358 }
7459
75- client . on ( 'spanEnd ' , span => {
60+ client . on ( 'enqueueSpan ' , span => {
7661 const spanTreeMapKey = getSpanTreeMapKey ( span ) ;
7762 const spanBuffer = spanTreeMap . get ( spanTreeMapKey ) ;
7863 if ( spanBuffer ) {
@@ -82,10 +67,14 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
8267 }
8368 } ) ;
8469
70+ client . on ( 'afterSpanEnd' , span => {
71+ captureSpan ( span , client ) ;
72+ } ) ;
73+
8574 // For now, we send all spans on local segment (root) span end.
8675 // TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
87- client . on ( 'segmentSpanEnd ' , segmentSpan => {
88- processAndSendSpans ( segmentSpan , {
76+ client . on ( 'afterSegmentSpanEnd ' , segmentSpan => {
77+ sendSegment ( segmentSpan , {
8978 spanTreeMap : spanTreeMap ,
9079 client,
9180 batchLimit : options . batchLimit ,
@@ -110,7 +99,7 @@ function getSpanTreeMapKey(span: Span): string {
11099 return `${ span . spanContext ( ) . traceId } -${ INTERNAL_getSegmentSpan ( span ) . spanContext ( ) . spanId } ` ;
111100}
112101
113- function processAndSendSpans (
102+ function sendSegment (
114103 segmentSpan : Span ,
115104 { client, spanTreeMap, batchLimit, beforeSendSpan } : SpanProcessingOptions ,
116105) : void {
@@ -123,54 +112,17 @@ function processAndSendSpans(
123112 return ;
124113 }
125114
126- const segmentSpanJson = spanToV2JSON ( segmentSpan ) ;
127-
128- for ( const span of spansOfTrace ) {
129- applyCommonSpanAttributes ( span , segmentSpanJson , client ) ;
130- }
131-
132- const { ignoreSpans } = client . getOptions ( ) ;
133-
134- // 1. Check if the entire span tree is ignored by ignoreSpans
135- if ( ignoreSpans ?. length && shouldIgnoreSpan ( segmentSpanJson , ignoreSpans ) ) {
136- client . recordDroppedEvent ( 'before_send' , 'span' , spansOfTrace . size ) ;
137- spanTreeMap . delete ( spanTreeMapKey ) ;
138- return ;
139- }
140-
141- const serializedSpans = Array . from ( spansOfTrace ?? [ ] ) . map ( s => {
142- const serialized = spanToV2JSON ( s ) ;
143- // remove internal span attributes we don't need to send.
144- delete serialized . attributes ?. [ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ] ;
145- return serialized ;
146- } ) ;
147-
148- const processedSpans = [ ] ;
149- let ignoredSpanCount = 0 ;
150-
151- for ( const span of serializedSpans ) {
152- // 2. Check if child spans should be ignored
153- const isChildSpan = span . span_id !== segmentSpan . spanContext ( ) . spanId ;
154- if ( ignoreSpans ?. length && isChildSpan && shouldIgnoreSpan ( span , ignoreSpans ) ) {
155- reparentChildSpans ( serializedSpans , span ) ;
156- ignoredSpanCount ++ ;
157- // drop this span by not adding it to the processedSpans array
158- continue ;
115+ const finalSpans = Array . from ( spansOfTrace ) . map ( span => {
116+ const spanJson = spanToV2JSON ( span ) ;
117+ if ( beforeSendSpan ) {
118+ return applyBeforeSendSpanCallback ( spanJson , beforeSendSpan ) ;
159119 }
160-
161- // 3. Apply beforeSendSpan callback
162- // TODO: validate beforeSendSpan result/pass in a copy and merge afterwards
163- const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback ( span , beforeSendSpan ) : span ;
164- processedSpans . push ( processedSpan ) ;
165- }
166-
167- if ( ignoredSpanCount ) {
168- client . recordDroppedEvent ( 'before_send' , 'span' , ignoredSpanCount ) ;
169- }
120+ return spanJson ;
121+ } ) ;
170122
171123 const batches : SpanV2JSON [ ] [ ] = [ ] ;
172- for ( let i = 0 ; i < processedSpans . length ; i += batchLimit ) {
173- batches . push ( processedSpans . slice ( i , i + batchLimit ) ) ;
124+ for ( let i = 0 ; i < finalSpans . length ; i += batchLimit ) {
125+ batches . push ( finalSpans . slice ( i , i + batchLimit ) ) ;
174126 }
175127
176128 DEBUG_BUILD && debug . log ( `Sending trace ${ traceId } in ${ batches . length } batch${ batches . length === 1 ? '' : 'es' } ` ) ;
@@ -190,35 +142,6 @@ function processAndSendSpans(
190142 spanTreeMap . delete ( spanTreeMapKey ) ;
191143}
192144
193- function applyCommonSpanAttributes ( span : Span , serializedSegmentSpan : SpanV2JSON , client : Client ) : void {
194- const sdk = client . getSdkMetadata ( ) ;
195- const { release, environment, sendDefaultPii } = client . getOptions ( ) ;
196-
197- const { isolationScope : spanIsolationScope , scope : spanScope } = getCapturedScopesOnSpan ( span ) ;
198-
199- const originalAttributeKeys = Object . keys ( spanToV2JSON ( span ) . attributes ?? { } ) ;
200-
201- const finalScopeData = getFinalScopeData ( spanIsolationScope , spanScope ) ;
202-
203- // avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
204- setAttributesIfNotPresent ( span , originalAttributeKeys , {
205- [ SEMANTIC_ATTRIBUTE_SENTRY_RELEASE ] : release ,
206- [ SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT ] : environment ,
207- [ SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME ] : serializedSegmentSpan . name ,
208- [ SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID ] : serializedSegmentSpan . span_id ,
209- [ SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME ] : sdk ?. sdk ?. name ,
210- [ SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION ] : sdk ?. sdk ?. version ,
211- ...( sendDefaultPii
212- ? {
213- [ SEMANTIC_ATTRIBUTE_USER_ID ] : finalScopeData . user ?. id ,
214- [ SEMANTIC_ATTRIBUTE_USER_EMAIL ] : finalScopeData . user ?. email ,
215- [ SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS ] : finalScopeData . user ?. ip_address ?? undefined ,
216- [ SEMANTIC_ATTRIBUTE_USER_USERNAME ] : finalScopeData . user ?. username ,
217- }
218- : { } ) ,
219- } ) ;
220- }
221-
222145function applyBeforeSendSpanCallback ( span : SpanV2JSON , beforeSendSpan : ( span : SpanV2JSON ) => SpanV2JSON ) : SpanV2JSON {
223146 const modifedSpan = beforeSendSpan ( span ) ;
224147 if ( ! modifedSpan ) {
@@ -227,23 +150,3 @@ function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: Sp
227150 }
228151 return modifedSpan ;
229152}
230-
231- function setAttributesIfNotPresent ( span : Span , originalAttributeKeys : string [ ] , newAttributes : SpanAttributes ) : void {
232- Object . keys ( newAttributes ) . forEach ( key => {
233- if ( ! originalAttributeKeys . includes ( key ) ) {
234- span . setAttribute ( key , newAttributes [ key ] ) ;
235- }
236- } ) ;
237- }
238-
239- // TODO: Extract this to a helper in core. It's used in multiple places.
240- function getFinalScopeData ( isolationScope : Scope | undefined , scope : Scope | undefined ) : ScopeData {
241- const finalScopeData = getGlobalScope ( ) . getScopeData ( ) ;
242- if ( isolationScope ) {
243- mergeScopeData ( finalScopeData , isolationScope . getScopeData ( ) ) ;
244- }
245- if ( scope ) {
246- mergeScopeData ( finalScopeData , scope . getScopeData ( ) ) ;
247- }
248- return finalScopeData ;
249- }
0 commit comments