@@ -109,6 +109,59 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
109
109
110
110
const client = getClient ( ) ;
111
111
112
+ function patchSpanEnd ( span : Span ) : void {
113
+ // We patch span.end to ensure we can run some things before the span is ended
114
+ // eslint-disable-next-line @typescript-eslint/unbound-method
115
+ span . end = new Proxy ( span . end , {
116
+ apply ( target , thisArg , args : Parameters < Span [ 'end' ] > ) {
117
+ if ( beforeSpanEnd ) {
118
+ beforeSpanEnd ( span ) ;
119
+ }
120
+
121
+ // If the span is non-recording, nothing more to do here...
122
+ // This is the case if tracing is enabled but this specific span was not sampled
123
+ if ( thisArg instanceof SentryNonRecordingSpan ) {
124
+ return ;
125
+ }
126
+
127
+ // Just ensuring that this keeps working, even if we ever have more arguments here
128
+ const [ definedEndTimestamp , ...rest ] = args ;
129
+ const timestamp = definedEndTimestamp || timestampInSeconds ( ) ;
130
+ const spanEndTimestamp = spanTimeInputToSeconds ( timestamp ) ;
131
+
132
+ // Ensure we end with the last span timestamp, if possible
133
+ const spans = getSpanDescendants ( span ) . filter ( child => child !== span ) ;
134
+
135
+ // If we have no spans, we just end, nothing else to do here
136
+ if ( ! spans . length ) {
137
+ onIdleSpanEnded ( spanEndTimestamp ) ;
138
+ return Reflect . apply ( target , thisArg , [ spanEndTimestamp , ...rest ] ) ;
139
+ }
140
+
141
+ const childEndTimestamps = spans
142
+ . map ( span => spanToJSON ( span ) . timestamp )
143
+ . filter ( timestamp => ! ! timestamp ) as number [ ] ;
144
+ const latestSpanEndTimestamp = childEndTimestamps . length ? Math . max ( ...childEndTimestamps ) : undefined ;
145
+
146
+ // In reality this should always exist here, but type-wise it may be undefined...
147
+ const spanStartTimestamp = spanToJSON ( span ) . start_timestamp ;
148
+
149
+ // The final endTimestamp should:
150
+ // * Never be before the span start timestamp
151
+ // * Be the latestSpanEndTimestamp, if there is one, and it is smaller than the passed span end timestamp
152
+ // * Otherwise be the passed end timestamp
153
+ // Final timestamp can never be after finalTimeout
154
+ const endTimestamp = Math . min (
155
+ spanStartTimestamp ? spanStartTimestamp + finalTimeout / 1000 : Infinity ,
156
+ Math . max ( spanStartTimestamp || - Infinity , Math . min ( spanEndTimestamp , latestSpanEndTimestamp || Infinity ) ) ,
157
+ ) ;
158
+
159
+ onIdleSpanEnded ( endTimestamp ) ;
160
+ return Reflect . apply ( target , thisArg , [ endTimestamp , ...rest ] ) ;
161
+ } ,
162
+ } ) ;
163
+ }
164
+
112
165
if ( ! client || ! hasTracingEnabled ( ) ) {
113
166
const span = new SentryNonRecordingSpan ( ) ;
114
167
@@ -118,64 +171,15 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
118
171
...getDynamicSamplingContextFromSpan ( span ) ,
119
172
} satisfies Partial < DynamicSamplingContext > ;
120
173
freezeDscOnSpan ( span , dsc ) ;
121
-
174
+ patchSpanEnd ( span ) ;
122
175
return span ;
123
176
}
124
177
125
178
const scope = getCurrentScope ( ) ;
126
179
const previousActiveSpan = getActiveSpan ( ) ;
127
180
const span = _startIdleSpan ( startSpanOptions ) ;
128
181
129
- // We patch span.end to ensure we can run some things before the span is ended
130
- // eslint-disable-next-line @typescript-eslint/unbound-method
131
- span . end = new Proxy ( span . end , {
132
- apply ( target , thisArg , args : Parameters < Span [ 'end' ] > ) {
133
- if ( beforeSpanEnd ) {
134
- beforeSpanEnd ( span ) ;
135
- }
136
-
137
- // If the span is non-recording, nothing more to do here...
138
- // This is the case if tracing is enabled but this specific span was not sampled
139
- if ( thisArg instanceof SentryNonRecordingSpan ) {
140
- return ;
141
- }
142
-
143
- // Just ensuring that this keeps working, even if we ever have more arguments here
144
- const [ definedEndTimestamp , ...rest ] = args ;
145
- const timestamp = definedEndTimestamp || timestampInSeconds ( ) ;
146
- const spanEndTimestamp = spanTimeInputToSeconds ( timestamp ) ;
147
-
148
- // Ensure we end with the last span timestamp, if possible
149
- const spans = getSpanDescendants ( span ) . filter ( child => child !== span ) ;
150
-
151
- // If we have no spans, we just end, nothing else to do here
152
- if ( ! spans . length ) {
153
- onIdleSpanEnded ( spanEndTimestamp ) ;
154
- return Reflect . apply ( target , thisArg , [ spanEndTimestamp , ...rest ] ) ;
155
- }
156
-
157
- const childEndTimestamps = spans
158
- . map ( span => spanToJSON ( span ) . timestamp )
159
- . filter ( timestamp => ! ! timestamp ) as number [ ] ;
160
- const latestSpanEndTimestamp = childEndTimestamps . length ? Math . max ( ...childEndTimestamps ) : undefined ;
161
-
162
- // In reality this should always exist here, but type-wise it may be undefined...
163
- const spanStartTimestamp = spanToJSON ( span ) . start_timestamp ;
164
-
165
- // The final endTimestamp should:
166
- // * Never be before the span start timestamp
167
- // * Be the latestSpanEndTimestamp, if there is one, and it is smaller than the passed span end timestamp
168
- // * Otherwise be the passed end timestamp
169
- // Final timestamp can never be after finalTimeout
170
- const endTimestamp = Math . min (
171
- spanStartTimestamp ? spanStartTimestamp + finalTimeout / 1000 : Infinity ,
172
- Math . max ( spanStartTimestamp || - Infinity , Math . min ( spanEndTimestamp , latestSpanEndTimestamp || Infinity ) ) ,
173
- ) ;
174
-
175
- onIdleSpanEnded ( endTimestamp ) ;
176
- return Reflect . apply ( target , thisArg , [ endTimestamp , ...rest ] ) ;
177
- } ,
178
- } ) ;
182
+ patchSpanEnd ( span ) ;
179
183
180
184
/**
181
185
* Cancels the existing idle timeout, if there is one.
0 commit comments