@@ -7,9 +7,21 @@ import type { ActivatedRouteSnapshot, Event, RouterState } from '@angular/router
77// eslint-disable-next-line @typescript-eslint/consistent-type-imports
88import { NavigationCancel , NavigationError , Router } from '@angular/router' ;
99import { NavigationEnd , NavigationStart , ResolveEnd } from '@angular/router' ;
10- import { WINDOW , getCurrentScope } from '@sentry/browser' ;
11- import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , spanToJSON } from '@sentry/core' ;
12- import type { Span , Transaction , TransactionContext } from '@sentry/types' ;
10+ import {
11+ WINDOW ,
12+ browserTracingIntegration as originalBrowserTracingIntegration ,
13+ getCurrentScope ,
14+ startBrowserTracingNavigationSpan ,
15+ } from '@sentry/browser' ;
16+ import {
17+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
18+ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
19+ getActiveSpan ,
20+ getClient ,
21+ spanToJSON ,
22+ startInactiveSpan ,
23+ } from '@sentry/core' ;
24+ import type { Integration , Span , Transaction , TransactionContext } from '@sentry/types' ;
1325import { logger , stripUrlQueryAndFragment , timestampInSeconds } from '@sentry/utils' ;
1426import type { Observable } from 'rxjs' ;
1527import { Subscription } from 'rxjs' ;
@@ -23,8 +35,12 @@ let instrumentationInitialized: boolean;
2335let stashedStartTransaction : ( context : TransactionContext ) => Transaction | undefined ;
2436let stashedStartTransactionOnLocationChange : boolean ;
2537
38+ let hooksBasedInstrumentation = false ;
39+
2640/**
2741 * Creates routing instrumentation for Angular Router.
42+ *
43+ * @deprecated Use `browserTracingIntegration()` instead, which includes Angular-specific instrumentation out of the box.
2844 */
2945export function routingInstrumentation (
3046 customStartTransaction : ( context : TransactionContext ) => Transaction | undefined ,
@@ -47,8 +63,35 @@ export function routingInstrumentation(
4763 }
4864}
4965
66+ /**
67+ * Creates routing instrumentation for Angular Router.
68+ *
69+ * @deprecated Use `browserTracingIntegration()` instead, which includes Angular-specific instrumentation out of the box.
70+ */
71+ // eslint-disable-next-line deprecation/deprecation
5072export const instrumentAngularRouting = routingInstrumentation ;
5173
74+ /**
75+ * A custom BrowserTracing integration for Angular.
76+ *
77+ * Use this integration in combination with `TraceService`
78+ */
79+ export function browserTracingIntegration (
80+ options : Parameters < typeof originalBrowserTracingIntegration > [ 0 ] = { } ,
81+ ) : Integration {
82+ // If the user opts out to set this up, we just don't initialize this.
83+ // That way, the TraceService will not actually do anything, functionally disabling this.
84+ if ( options . instrumentNavigation !== false ) {
85+ instrumentationInitialized = true ;
86+ hooksBasedInstrumentation = true ;
87+ }
88+
89+ return originalBrowserTracingIntegration ( {
90+ ...options ,
91+ instrumentNavigation : false ,
92+ } ) ;
93+ }
94+
5295/**
5396 * Grabs active transaction off scope.
5497 *
@@ -74,7 +117,44 @@ export class TraceService implements OnDestroy {
74117 return ;
75118 }
76119
120+ if ( this . _routingSpan ) {
121+ this . _routingSpan . end ( ) ;
122+ this . _routingSpan = null ;
123+ }
124+
125+ const client = getClient ( ) ;
77126 const strippedUrl = stripUrlQueryAndFragment ( navigationEvent . url ) ;
127+
128+ if ( client && hooksBasedInstrumentation ) {
129+ if ( ! getActiveSpan ( ) ) {
130+ startBrowserTracingNavigationSpan ( client , {
131+ name : strippedUrl ,
132+ op : 'navigation' ,
133+ origin : 'auto.navigation.angular' ,
134+ attributes : {
135+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' ,
136+ } ,
137+ } ) ;
138+ }
139+
140+ // eslint-disable-next-line deprecation/deprecation
141+ this . _routingSpan =
142+ startInactiveSpan ( {
143+ name : `${ navigationEvent . url } ` ,
144+ op : ANGULAR_ROUTING_OP ,
145+ origin : 'auto.ui.angular' ,
146+ tags : {
147+ 'routing.instrumentation' : '@sentry/angular' ,
148+ url : strippedUrl ,
149+ ...( navigationEvent . navigationTrigger && {
150+ navigationTrigger : navigationEvent . navigationTrigger ,
151+ } ) ,
152+ } ,
153+ } ) || null ;
154+
155+ return ;
156+ }
157+
78158 // eslint-disable-next-line deprecation/deprecation
79159 let activeTransaction = getActiveTransaction ( ) ;
80160
@@ -90,9 +170,6 @@ export class TraceService implements OnDestroy {
90170 }
91171
92172 if ( activeTransaction ) {
93- if ( this . _routingSpan ) {
94- this . _routingSpan . end ( ) ;
95- }
96173 // eslint-disable-next-line deprecation/deprecation
97174 this . _routingSpan = activeTransaction . startChild ( {
98175 description : `${ navigationEvent . url } ` ,
@@ -132,6 +209,7 @@ export class TraceService implements OnDestroy {
132209 if ( transaction && attributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] === 'url' ) {
133210 transaction . updateName ( route ) ;
134211 transaction . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , 'route' ) ;
212+ transaction . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , `auto.${ spanToJSON ( transaction ) . op } .angular` ) ;
135213 }
136214 } ) ,
137215 ) ;
0 commit comments