Skip to content

Commit 1fe5c01

Browse files
authored
ref(ember): Use new browserTracingIntegration() under the hood (#10373)
Refactors the usage of `BrowserTracing()` for Ember. There it is easy to refactor this because we do not expose this to the user - we automatically add the browsertracing integration based on configuration. This depends on #10372.
1 parent a37fabd commit 1fe5c01

File tree

4 files changed

+69
-90
lines changed

4 files changed

+69
-90
lines changed

packages/ember/addon/instance-initializers/sentry-performance.ts

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,13 @@ import type { EmberRunQueues } from '@ember/runloop/-private/types';
88
import { getOwnConfig, isTesting, macroCondition } from '@embroider/macros';
99
import * as Sentry from '@sentry/browser';
1010
import type { ExtendedBackburner } from '@sentry/ember/runloop';
11-
import type { Span, Transaction } from '@sentry/types';
11+
import type { Span } from '@sentry/types';
1212
import { GLOBAL_OBJ, browserPerformanceTimeOrigin, timestampInSeconds } from '@sentry/utils';
1313

1414
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
1515
import type { BrowserClient } from '..';
1616
import { getActiveSpan, startInactiveSpan } from '..';
17-
import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig, StartTransactionFunction } from '../types';
18-
19-
type SentryTestRouterService = RouterService & {
20-
_startTransaction?: StartTransactionFunction;
21-
_sentryInstrumented?: boolean;
22-
};
17+
import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig } from '../types';
2318

2419
function getSentryConfig(): EmberSentryConfig {
2520
const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig;
@@ -98,26 +93,25 @@ export function _instrumentEmberRouter(
9893
routerService: RouterService,
9994
routerMain: EmberRouterMain,
10095
config: EmberSentryConfig,
101-
startTransaction: StartTransactionFunction,
102-
startTransactionOnPageLoad?: boolean,
103-
): {
104-
startTransaction: StartTransactionFunction;
105-
} {
96+
): void {
10697
const { disableRunloopPerformance } = config;
10798
const location = routerMain.location;
108-
let activeTransaction: Transaction | undefined;
99+
let activeRootSpan: Span | undefined;
109100
let transitionSpan: Span | undefined;
110101

102+
// Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
103+
const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
111104
const url = getLocationURL(location);
112105

113-
if (macroCondition(isTesting())) {
114-
(routerService as SentryTestRouterService)._sentryInstrumented = true;
115-
(routerService as SentryTestRouterService)._startTransaction = startTransaction;
106+
const client = Sentry.getClient<BrowserClient>();
107+
108+
if (!client) {
109+
return;
116110
}
117111

118-
if (startTransactionOnPageLoad && url) {
112+
if (url && browserTracingOptions.startTransactionOnPageLoad !== false) {
119113
const routeInfo = routerService.recognize(url);
120-
activeTransaction = startTransaction({
114+
Sentry.startBrowserTracingPageLoadSpan(client, {
121115
name: `route:${routeInfo.name}`,
122116
op: 'pageload',
123117
origin: 'auto.pageload.ember',
@@ -127,20 +121,26 @@ export function _instrumentEmberRouter(
127121
'routing.instrumentation': '@sentry/ember',
128122
},
129123
});
124+
activeRootSpan = getActiveSpan();
130125
}
131126

132127
const finishActiveTransaction = (_: unknown, nextInstance: unknown): void => {
133128
if (nextInstance) {
134129
return;
135130
}
136-
activeTransaction?.end();
131+
activeRootSpan?.end();
137132
getBackburner().off('end', finishActiveTransaction);
138133
};
139134

135+
if (browserTracingOptions.startTransactionOnLocationChange === false) {
136+
return;
137+
}
138+
140139
routerService.on('routeWillChange', (transition: Transition) => {
141140
const { fromRoute, toRoute } = getTransitionInformation(transition, routerService);
142-
activeTransaction?.end();
143-
activeTransaction = startTransaction({
141+
activeRootSpan?.end();
142+
143+
Sentry.startBrowserTracingNavigationSpan(client, {
144144
name: `route:${toRoute}`,
145145
op: 'navigation',
146146
origin: 'auto.navigation.ember',
@@ -150,6 +150,9 @@ export function _instrumentEmberRouter(
150150
'routing.instrumentation': '@sentry/ember',
151151
},
152152
});
153+
154+
activeRootSpan = getActiveSpan();
155+
153156
transitionSpan = startInactiveSpan({
154157
attributes: {
155158
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
@@ -160,22 +163,18 @@ export function _instrumentEmberRouter(
160163
});
161164

162165
routerService.on('routeDidChange', () => {
163-
if (!transitionSpan || !activeTransaction) {
166+
if (!transitionSpan || !activeRootSpan) {
164167
return;
165168
}
166169
transitionSpan.end();
167170

168171
if (disableRunloopPerformance) {
169-
activeTransaction.end();
172+
activeRootSpan.end();
170173
return;
171174
}
172175

173176
getBackburner().on('end', finishActiveTransaction);
174177
});
175-
176-
return {
177-
startTransaction,
178-
};
179178
}
180179

181180
function _instrumentEmberRunloop(config: EmberSentryConfig): void {
@@ -411,61 +410,63 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance)
411410
// Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
412411
const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
413412

414-
const { BrowserTracing } = await import('@sentry/browser');
413+
const { browserTracingIntegration } = await import('@sentry/browser');
415414

416415
const idleTimeout = config.transitionTimeout || 5000;
417416

418-
const browserTracing = new BrowserTracing({
419-
routingInstrumentation: (customStartTransaction, startTransactionOnPageLoad) => {
420-
// eslint-disable-next-line ember/no-private-routing-service
421-
const routerMain = appInstance.lookup('router:main') as EmberRouterMain;
422-
let routerService = appInstance.lookup('service:router') as RouterService & {
423-
externalRouter?: RouterService;
424-
_hasMountedSentryPerformanceRouting?: boolean;
425-
};
426-
427-
if (routerService.externalRouter) {
428-
// Using ember-engines-router-service in an engine.
429-
routerService = routerService.externalRouter;
430-
}
431-
if (routerService._hasMountedSentryPerformanceRouting) {
432-
// Routing listens to route changes on the main router, and should not be initialized multiple times per page.
433-
return;
434-
}
435-
if (!routerService.recognize) {
436-
// Router is missing critical functionality to limit cardinality of the transaction names.
437-
return;
438-
}
439-
routerService._hasMountedSentryPerformanceRouting = true;
440-
_instrumentEmberRouter(routerService, routerMain, config, customStartTransaction, startTransactionOnPageLoad);
441-
},
417+
const browserTracing = browserTracingIntegration({
442418
idleTimeout,
443419
...browserTracingOptions,
420+
instrumentNavigation: false,
421+
instrumentPageLoad: false,
444422
});
445423

446-
if (macroCondition(isTesting())) {
447-
const client = Sentry.getClient();
448-
449-
if (
450-
client &&
451-
(client as BrowserClient).getIntegrationByName &&
452-
(client as BrowserClient).getIntegrationByName('BrowserTracing')
453-
) {
454-
// Initializers are called more than once in tests, causing the integrations to not be setup correctly.
455-
return;
456-
}
457-
}
424+
const client = Sentry.getClient<BrowserClient>();
425+
426+
const isAlreadyInitialized = macroCondition(isTesting()) ? !!client?.getIntegrationByName('BrowserTracing') : false;
458427

459-
const client = Sentry.getClient();
460428
if (client && client.addIntegration) {
461429
client.addIntegration(browserTracing);
462430
}
463431

432+
// We _always_ call this, as it triggers the page load & navigation spans
433+
_instrumentNavigation(appInstance, config);
434+
435+
// Skip instrumenting the stuff below again in tests, as these are not reset between tests
436+
if (isAlreadyInitialized) {
437+
return;
438+
}
439+
464440
_instrumentEmberRunloop(config);
465441
_instrumentComponents(config);
466442
_instrumentInitialLoad(config);
467443
}
468444

445+
function _instrumentNavigation(appInstance: ApplicationInstance, config: EmberSentryConfig): void {
446+
// eslint-disable-next-line ember/no-private-routing-service
447+
const routerMain = appInstance.lookup('router:main') as EmberRouterMain;
448+
let routerService = appInstance.lookup('service:router') as RouterService & {
449+
externalRouter?: RouterService;
450+
_hasMountedSentryPerformanceRouting?: boolean;
451+
};
452+
453+
if (routerService.externalRouter) {
454+
// Using ember-engines-router-service in an engine.
455+
routerService = routerService.externalRouter;
456+
}
457+
if (routerService._hasMountedSentryPerformanceRouting) {
458+
// Routing listens to route changes on the main router, and should not be initialized multiple times per page.
459+
return;
460+
}
461+
if (!routerService.recognize) {
462+
// Router is missing critical functionality to limit cardinality of the transaction names.
463+
return;
464+
}
465+
466+
routerService._hasMountedSentryPerformanceRouting = true;
467+
_instrumentEmberRouter(routerService, routerMain, config);
468+
}
469+
469470
export default {
470471
initialize,
471472
};

packages/ember/addon/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface EmberRouterMain {
3131
rootURL: string;
3232
};
3333
}
34-
34+
/** @deprecated This will be removed in v8. */
3535
export type StartTransactionFunction = (context: TransactionContext) => Transaction | undefined;
3636

3737
export type GlobalConfig = {

packages/ember/tests/helpers/setup-sentry.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,21 @@
1-
import type RouterService from '@ember/routing/router-service';
21
import type { TestContext } from '@ember/test-helpers';
32
import { resetOnerror, setupOnerror } from '@ember/test-helpers';
4-
import { _instrumentEmberRouter } from '@sentry/ember/instance-initializers/sentry-performance';
5-
import type { EmberRouterMain, EmberSentryConfig, StartTransactionFunction } from '@sentry/ember/types';
63
import sinon from 'sinon';
74

8-
// Keep a reference to the original startTransaction as the application gets re-initialized and setup for
9-
// the integration doesn't occur again after the first time.
10-
let _routerStartTransaction: StartTransactionFunction | undefined;
11-
125
export type SentryTestContext = TestContext & {
136
errorMessages: string[];
147
fetchStub: sinon.SinonStub;
158
qunitOnUnhandledRejection: sinon.SinonStub;
169
_windowOnError: OnErrorEventHandler;
1710
};
1811

19-
type SentryRouterService = RouterService & {
20-
_startTransaction: StartTransactionFunction;
21-
_sentryInstrumented?: boolean;
22-
};
23-
2412
export function setupSentryTest(hooks: NestedHooks): void {
2513
hooks.beforeEach(async function (this: SentryTestContext) {
2614
await window._sentryPerformanceLoad;
2715
window._sentryTestEvents = [];
2816
const errorMessages: string[] = [];
2917
this.errorMessages = errorMessages;
3018

31-
// eslint-disable-next-line ember/no-private-routing-service
32-
const routerMain = this.owner.lookup('router:main') as EmberRouterMain;
33-
const routerService = this.owner.lookup('service:router') as SentryRouterService;
34-
35-
if (routerService._sentryInstrumented) {
36-
_routerStartTransaction = routerService._startTransaction;
37-
} else if (_routerStartTransaction) {
38-
_instrumentEmberRouter(routerService, routerMain, {} as EmberSentryConfig, _routerStartTransaction);
39-
}
40-
4119
/**
4220
* Stub out fetch function to assert on Sentry calls.
4321
*/

packages/ember/tests/helpers/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export function assertSentryTransactions(
5858
const sentryTestEvents = getTestSentryTransactions();
5959
const event = sentryTestEvents[callNumber];
6060

61-
assert.ok(event);
62-
assert.ok(event.spans);
61+
assert.ok(event, 'event exists');
62+
assert.ok(event.spans, 'event has spans');
6363

6464
const spans = event.spans || [];
6565

0 commit comments

Comments
 (0)