@@ -52,6 +52,7 @@ interface TracingOptions {
5252 * Default: 500
5353 */
5454 idleTimeout : number ;
55+
5556 /**
5657 * Flag to enable/disable creation of `navigation` transaction on history changes. Useful for react applications with
5758 * a router.
@@ -60,6 +61,13 @@ interface TracingOptions {
6061 */
6162 startTransactionOnLocationChange : boolean ;
6263
64+ /**
65+ * Flag to enable/disable creation of `pageload` transaction on first pageload.
66+ *
67+ * Default: true
68+ */
69+ startTransactionOnPageLoad : boolean ;
70+
6371 /**
6472 * The maximum duration of a transaction before it will be marked as "deadline_exceeded".
6573 * If you never want to mark a transaction set it to 0.
@@ -137,8 +145,6 @@ export class Tracing implements Integration {
137145
138146 public static _activities : { [ key : number ] : Activity } = { } ;
139147
140- private static _idleTransactionEndTimestamp : number = 0 ;
141-
142148 private readonly _emitOptionsWarning : boolean = false ;
143149
144150 private static _performanceCursor : number = 0 ;
@@ -174,6 +180,7 @@ export class Tracing implements Integration {
174180 ) ;
175181 } ,
176182 startTransactionOnLocationChange : true ,
183+ startTransactionOnPageLoad : true ,
177184 traceFetch : true ,
178185 traceXHR : true ,
179186 tracingOrigins : defaultTracingOrigins ,
@@ -202,7 +209,7 @@ export class Tracing implements Integration {
202209 }
203210
204211 // Starting pageload transaction
205- if ( global . location && global . location . href ) {
212+ if ( global . location && global . location . href && Tracing . options && Tracing . options . startTransactionOnPageLoad ) {
206213 // Use `${global.location.href}` as transaction name
207214 Tracing . startIdleTransaction ( {
208215 name : global . location . href ,
@@ -285,7 +292,7 @@ export class Tracing implements Integration {
285292 ) ;
286293 Tracing . _activeTransaction . setStatus ( SpanStatus . DeadlineExceeded ) ;
287294 Tracing . _activeTransaction . setTag ( 'heartbeat' , 'failed' ) ;
288- Tracing . finishIdleTransaction ( ) ;
295+ Tracing . finishIdleTransaction ( timestampWithMs ( ) ) ;
289296 }
290297 }
291298 Tracing . _prevHeartbeatString = heartbeatString ;
@@ -303,7 +310,7 @@ export class Tracing implements Integration {
303310 Tracing . _log ( `[Tracing] Transaction: ${ SpanStatus . Cancelled } -> since tab moved to the background` ) ;
304311 Tracing . _activeTransaction . setStatus ( SpanStatus . Cancelled ) ;
305312 Tracing . _activeTransaction . setTag ( 'visibilitychange' , 'document.hidden' ) ;
306- Tracing . finishIdleTransaction ( ) ;
313+ Tracing . finishIdleTransaction ( timestampWithMs ( ) ) ;
307314 }
308315 } ) ;
309316 }
@@ -403,7 +410,6 @@ export class Tracing implements Integration {
403410 message : safeJoin ( args , ' ' ) ,
404411 type : 'debug' ,
405412 } ) ;
406- return ;
407413 }
408414 }
409415 logger . log ( ...args ) ;
@@ -413,11 +419,6 @@ export class Tracing implements Integration {
413419 * Starts a Transaction waiting for activity idle to finish
414420 */
415421 public static startIdleTransaction ( transactionContext : TransactionContext ) : Transaction | undefined {
416- // If we already have an active transaction it means one of two things
417- // a) The user did rapid navigation changes and didn't wait until the transaction was finished
418- // b) A activity wasn't popped correctly and therefore the transaction is stalling
419- Tracing . finishIdleTransaction ( ) ;
420-
421422 Tracing . _log ( '[Tracing] startIdleTransaction' ) ;
422423
423424 const _getCurrentHub = Tracing . _getCurrentHub ;
@@ -448,27 +449,44 @@ export class Tracing implements Integration {
448449 /**
449450 * Finshes the current active transaction
450451 */
451- public static finishIdleTransaction ( ) : void {
452+ public static finishIdleTransaction ( endTimestamp : number ) : void {
452453 const active = Tracing . _activeTransaction ;
453454 if ( active ) {
455+ Tracing . _log ( '[Tracing] finishing IdleTransaction' , new Date ( endTimestamp * 1000 ) . toISOString ( ) ) ;
454456 Tracing . _addPerformanceEntries ( active ) ;
455- Tracing . _log ( '[Tracing] finishIdleTransaction' ) ;
456457
457458 if ( active . spanRecorder ) {
458- const timeout = ( Tracing . options && Tracing . options . idleTimeout ) || 100 ;
459- active . spanRecorder . spans = active . spanRecorder . spans . filter ( ( finishedSpan : Span ) => {
460- const keepSpan = finishedSpan . startTimestamp < Tracing . _idleTransactionEndTimestamp + timeout ;
459+ active . spanRecorder . spans = active . spanRecorder . spans . filter ( ( span : Span ) => {
460+ // If we are dealing with the transaction itself, we just return it
461+ if ( span . spanId === active . spanId ) {
462+ return span ;
463+ }
464+
465+ // We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early
466+ if ( ! span . endTimestamp ) {
467+ span . endTimestamp = endTimestamp ;
468+ span . setStatus ( SpanStatus . Cancelled ) ;
469+ Tracing . _log ( '[Tracing] cancelling span since transaction ended early' , JSON . stringify ( span , undefined , 2 ) ) ;
470+ }
471+
472+ // We remove all spans that happend after the end of the transaction
473+ // This is here to prevent super long transactions and timing issues
474+ const keepSpan = span . startTimestamp < endTimestamp ;
461475 if ( ! keepSpan ) {
462476 Tracing . _log (
463477 '[Tracing] discarding Span since it happened after Transaction was finished' ,
464- finishedSpan . toJSON ( ) ,
478+ JSON . stringify ( span , undefined , 2 ) ,
465479 ) ;
466480 }
467481 return keepSpan ;
468482 } ) ;
469483 }
484+
485+ Tracing . _log ( '[Tracing] flushing IdleTransaction' ) ;
470486 active . finish ( ) ;
471487 Tracing . _resetActiveTransaction ( ) ;
488+ } else {
489+ Tracing . _log ( '[Tracing] No active IdleTransaction' ) ;
472490 }
473491 }
474492
@@ -491,29 +509,29 @@ export class Tracing implements Integration {
491509
492510 // tslint:disable-next-line: completed-docs
493511 function addPerformanceNavigationTiming ( parent : Span , entry : { [ key : string ] : number } , event : string ) : void {
494- const span = parent . startChild ( {
512+ parent . startChild ( {
495513 description : event ,
514+ endTimestamp : timeOrigin + Tracing . _msToSec ( entry [ `${ event } End` ] ) ,
496515 op : 'browser' ,
516+ startTimestamp : timeOrigin + Tracing . _msToSec ( entry [ `${ event } Start` ] ) ,
497517 } ) ;
498- span . startTimestamp = timeOrigin + Tracing . _msToSec ( entry [ `${ event } Start` ] ) ;
499- span . endTimestamp = timeOrigin + Tracing . _msToSec ( entry [ `${ event } End` ] ) ;
500518 }
501519
502520 // tslint:disable-next-line: completed-docs
503521 function addRequest ( parent : Span , entry : { [ key : string ] : number } ) : void {
504- const request = parent . startChild ( {
522+ parent . startChild ( {
505523 description : 'request' ,
524+ endTimestamp : timeOrigin + Tracing . _msToSec ( entry . responseEnd ) ,
506525 op : 'browser' ,
526+ startTimestamp : timeOrigin + Tracing . _msToSec ( entry . requestStart ) ,
507527 } ) ;
508- request . startTimestamp = timeOrigin + Tracing . _msToSec ( entry . requestStart ) ;
509- request . endTimestamp = timeOrigin + Tracing . _msToSec ( entry . responseEnd ) ;
510528
511- const response = parent . startChild ( {
529+ parent . startChild ( {
512530 description : 'response' ,
531+ endTimestamp : timeOrigin + Tracing . _msToSec ( entry . responseEnd ) ,
513532 op : 'browser' ,
533+ startTimestamp : timeOrigin + Tracing . _msToSec ( entry . responseStart ) ,
514534 } ) ;
515- response . startTimestamp = timeOrigin + Tracing . _msToSec ( entry . responseStart ) ;
516- response . endTimestamp = timeOrigin + Tracing . _msToSec ( entry . responseEnd ) ;
517535 }
518536
519537 let entryScriptSrc : string | undefined ;
@@ -599,16 +617,15 @@ export class Tracing implements Integration {
599617 } ) ;
600618
601619 if ( entryScriptStartEndTime !== undefined && tracingInitMarkStartTime !== undefined ) {
602- const evaluation = transactionSpan . startChild ( {
620+ transactionSpan . startChild ( {
603621 description : 'evaluation' ,
622+ endTimestamp : tracingInitMarkStartTime ,
604623 op : `script` ,
624+ startTimestamp : entryScriptStartEndTime ,
605625 } ) ;
606- evaluation . startTimestamp = entryScriptStartEndTime ;
607- evaluation . endTimestamp = tracingInitMarkStartTime ;
608626 }
609627
610628 Tracing . _performanceCursor = Math . max ( performance . getEntries ( ) . length - 1 , 0 ) ;
611-
612629 // tslint:enable: no-unsafe-any
613630 }
614631
@@ -756,9 +773,11 @@ export class Tracing implements Integration {
756773 if ( count === 0 && Tracing . _activeTransaction ) {
757774 const timeout = Tracing . options && Tracing . options . idleTimeout ;
758775 Tracing . _log ( `[Tracing] Flushing Transaction in ${ timeout } ms` ) ;
759- Tracing . _idleTransactionEndTimestamp = timestampWithMs ( ) ;
776+ // We need to add the timeout here to have the real endtimestamp of the transaction
777+ // Remeber timestampWithMs is in seconds, timeout is in ms
778+ const end = timestampWithMs ( ) + timeout / 1000 ;
760779 setTimeout ( ( ) => {
761- Tracing . finishIdleTransaction ( ) ;
780+ Tracing . finishIdleTransaction ( end ) ;
762781 } , timeout ) ;
763782 }
764783 }
@@ -871,6 +890,7 @@ function fetchCallback(handlerData: { [key: string]: any }): void {
871890 */
872891function historyCallback ( _ : { [ key : string ] : any } ) : void {
873892 if ( Tracing . options . startTransactionOnLocationChange && global && global . location ) {
893+ Tracing . finishIdleTransaction ( timestampWithMs ( ) ) ;
874894 Tracing . startIdleTransaction ( {
875895 name : global . location . href ,
876896 op : 'navigation' ,
0 commit comments