1
- /* eslint-disable complexity */
2
- import type { Client , Event , Integration , SpanJSON , TransactionEvent } from '@sentry/core' ;
1
+ /* eslint-disable complexity, max-lines */
2
+ import type { Client , Event , Integration , Span , SpanJSON , TransactionEvent } from '@sentry/core' ;
3
3
import {
4
4
getCapturedScopesOnSpan ,
5
5
getClient ,
@@ -17,7 +17,7 @@ import {
17
17
} from '../../measurements' ;
18
18
import type { NativeAppStartResponse } from '../../NativeRNSentry' ;
19
19
import type { ReactNativeClientOptions } from '../../options' ;
20
- import { convertSpanToTransaction , setEndTimeValue } from '../../utils/span' ;
20
+ import { convertSpanToTransaction , isRootSpan , setEndTimeValue } from '../../utils/span' ;
21
21
import { NATIVE } from '../../wrapper' ;
22
22
import {
23
23
APP_START_COLD as APP_START_COLD_OP ,
@@ -136,16 +136,18 @@ export const appStartIntegration = ({
136
136
let _client : Client | undefined = undefined ;
137
137
let isEnabled = true ;
138
138
let appStartDataFlushed = false ;
139
+ let firstStartedActiveRootSpanId : string | undefined = undefined ;
139
140
140
141
const setup = ( client : Client ) : void => {
141
142
_client = client ;
142
- const clientOptions = client . getOptions ( ) as ReactNativeClientOptions ;
143
+ const { enableAppStartTracking } = client . getOptions ( ) as ReactNativeClientOptions ;
143
144
144
- const { enableAppStartTracking } = clientOptions ;
145
145
if ( ! enableAppStartTracking ) {
146
146
isEnabled = false ;
147
147
logger . warn ( '[AppStart] App start tracking is disabled.' ) ;
148
148
}
149
+
150
+ client . on ( 'spanStart' , recordFirstStartedActiveRootSpanId ) ;
149
151
} ;
150
152
151
153
const afterAllSetup = ( _client : Client ) : void => {
@@ -167,6 +169,27 @@ export const appStartIntegration = ({
167
169
return event ;
168
170
} ;
169
171
172
+ const recordFirstStartedActiveRootSpanId = ( rootSpan : Span ) : void => {
173
+ if ( firstStartedActiveRootSpanId ) {
174
+ return ;
175
+ }
176
+
177
+ if ( ! isRootSpan ( rootSpan ) ) {
178
+ return ;
179
+ }
180
+
181
+ setFirstStartedActiveRootSpanId ( rootSpan . spanContext ( ) . spanId ) ;
182
+ } ;
183
+
184
+ /**
185
+ * For testing purposes only.
186
+ * @private
187
+ */
188
+ const setFirstStartedActiveRootSpanId = ( spanId : string | undefined ) : void => {
189
+ firstStartedActiveRootSpanId = spanId ;
190
+ logger . debug ( '[AppStart] First started active root span id recorded.' , firstStartedActiveRootSpanId ) ;
191
+ } ;
192
+
170
193
async function captureStandaloneAppStart ( ) : Promise < void > {
171
194
if ( ! standalone ) {
172
195
logger . debug (
@@ -212,11 +235,23 @@ export const appStartIntegration = ({
212
235
return ;
213
236
}
214
237
238
+ if ( ! firstStartedActiveRootSpanId ) {
239
+ logger . warn ( '[AppStart] No first started active root span id recorded. Can not attach app start.' ) ;
240
+ return ;
241
+ }
242
+
215
243
if ( ! event . contexts || ! event . contexts . trace ) {
216
244
logger . warn ( '[AppStart] Transaction event is missing trace context. Can not attach app start.' ) ;
217
245
return ;
218
246
}
219
247
248
+ if ( firstStartedActiveRootSpanId !== event . contexts . trace . span_id ) {
249
+ logger . warn (
250
+ '[AppStart] First started active root span id does not match the transaction event span id. Can not attached app start.' ,
251
+ ) ;
252
+ return ;
253
+ }
254
+
220
255
const appStart = await NATIVE . fetchNativeAppStart ( ) ;
221
256
if ( ! appStart ) {
222
257
logger . warn ( '[AppStart] Failed to retrieve the app start metrics from the native layer.' ) ;
@@ -332,7 +367,8 @@ export const appStartIntegration = ({
332
367
afterAllSetup,
333
368
processEvent,
334
369
captureStandaloneAppStart,
335
- } ;
370
+ setFirstStartedActiveRootSpanId,
371
+ } as AppStartIntegration ;
336
372
} ;
337
373
338
374
function setSpanDurationAsMeasurementOnTransactionEvent ( event : TransactionEvent , label : string , span : SpanJSON ) : void {
0 commit comments