Skip to content

Commit d92114b

Browse files
authored
fix(ember): Ensure only one client is created & Replay works (#7712)
1 parent de75a63 commit d92114b

File tree

8 files changed

+80
-52
lines changed

8 files changed

+80
-52
lines changed

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

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { run, _backburner, scheduleOnce } from '@ember/runloop';
33
import { subscribe } from '@ember/instrumentation';
44
import * as Sentry from '@sentry/browser';
55
import { ExtendedBackburner } from '@sentry/ember/runloop';
6-
import { Span, Transaction, Integration } from '@sentry/types';
6+
import { Span, Transaction } from '@sentry/types';
77
import { EmberRunQueues } from '@ember/runloop/-private/types';
88
import { getActiveTransaction } from '..';
99
import { browserPerformanceTimeOrigin, GLOBAL_OBJ, timestampWithMs } from '@sentry/utils';
1010
import { macroCondition, isTesting, getOwnConfig } from '@embroider/macros';
1111
import { EmberSentryConfig, GlobalConfig, OwnConfig } from '../types';
1212
import RouterService from '@ember/routing/router-service';
13+
import type { BaseClient } from '@sentry/core';
1314

1415
function getSentryConfig() {
1516
const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig;
@@ -390,58 +391,49 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance)
390391

391392
const idleTimeout = config.transitionTimeout || 5000;
392393

393-
const existingIntegrations = (sentryConfig['integrations'] || []) as Integration[];
394-
395-
sentryConfig['integrations'] = [
396-
...existingIntegrations,
397-
new BrowserTracing({
398-
routingInstrumentation: (customStartTransaction, startTransactionOnPageLoad) => {
399-
const routerMain = appInstance.lookup('router:main');
400-
let routerService = appInstance.lookup('service:router') as
401-
| RouterService & { externalRouter?: RouterService; _hasMountedSentryPerformanceRouting?: boolean };
402-
403-
if (routerService.externalRouter) {
404-
// Using ember-engines-router-service in an engine.
405-
routerService = routerService.externalRouter;
406-
}
407-
if (routerService._hasMountedSentryPerformanceRouting) {
408-
// Routing listens to route changes on the main router, and should not be initialized multiple times per page.
409-
return;
410-
}
411-
if (!routerService.recognize) {
412-
// Router is missing critical functionality to limit cardinality of the transaction names.
413-
return;
414-
}
415-
routerService._hasMountedSentryPerformanceRouting = true;
416-
_instrumentEmberRouter(routerService, routerMain, config, customStartTransaction, startTransactionOnPageLoad);
417-
},
418-
idleTimeout,
419-
...browserTracingOptions,
420-
}),
421-
];
422-
423-
class FakeBrowserTracingClass {
424-
static id = 'BrowserTracing';
425-
public name = FakeBrowserTracingClass.id;
426-
setupOnce() {
427-
// noop - We're just faking this class for a lookup
394+
const browserTracing = new BrowserTracing({
395+
routingInstrumentation: (customStartTransaction, startTransactionOnPageLoad) => {
396+
const routerMain = appInstance.lookup('router:main');
397+
let routerService = appInstance.lookup('service:router') as
398+
| RouterService & { externalRouter?: RouterService; _hasMountedSentryPerformanceRouting?: boolean };
399+
400+
if (routerService.externalRouter) {
401+
// Using ember-engines-router-service in an engine.
402+
routerService = routerService.externalRouter;
403+
}
404+
if (routerService._hasMountedSentryPerformanceRouting) {
405+
// Routing listens to route changes on the main router, and should not be initialized multiple times per page.
406+
return;
407+
}
408+
if (!routerService.recognize) {
409+
// Router is missing critical functionality to limit cardinality of the transaction names.
410+
return;
411+
}
412+
routerService._hasMountedSentryPerformanceRouting = true;
413+
_instrumentEmberRouter(routerService, routerMain, config, customStartTransaction, startTransactionOnPageLoad);
414+
},
415+
idleTimeout,
416+
...browserTracingOptions,
417+
});
418+
419+
if (macroCondition(isTesting())) {
420+
const client = Sentry.getCurrentHub().getClient();
421+
422+
if (
423+
client &&
424+
(client as BaseClient<any>).getIntegrationById &&
425+
(client as BaseClient<any>).getIntegrationById('BrowserTracing')
426+
) {
427+
// Initializers are called more than once in tests, causing the integrations to not be setup correctly.
428+
return;
428429
}
429430
}
430431

431-
if (
432-
isTesting() &&
433-
Sentry.getCurrentHub()?.getIntegration(
434-
// This is a temporary hack because the BrowserTracing integration cannot have a static `id` field for tree
435-
// shaking reasons. However, `getIntegration` needs that field.
436-
FakeBrowserTracingClass,
437-
)
438-
) {
439-
// Initializers are called more than once in tests, causing the integrations to not be setup correctly.
440-
return;
432+
const client = Sentry.getCurrentHub().getClient();
433+
if (client && client.addIntegration) {
434+
client.addIntegration(browserTracing);
441435
}
442436

443-
Sentry.init(sentryConfig); // Call init again to rebind client with new integration list in addition to the defaults
444-
445437
_instrumentEmberRunloop(config);
446438
_instrumentComponents(config);
447439
_instrumentInitialLoad(config);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { test, module } from 'qunit';
2+
import { setupApplicationTest } from 'ember-qunit';
3+
import { visit } from '@ember/test-helpers';
4+
import { setupSentryTest } from '../helpers/setup-sentry';
5+
import * as Sentry from '@sentry/ember';
6+
7+
module('Acceptance | Sentry Session Replay', function (hooks) {
8+
setupApplicationTest(hooks);
9+
setupSentryTest(hooks);
10+
11+
test('Test replay', async function (assert) {
12+
await visit('/replay');
13+
14+
const replay = Sentry.getCurrentHub().getIntegration(Sentry.Replay);
15+
assert.ok(replay);
16+
17+
assert.true(replay._replay.isEnabled());
18+
assert.false(replay._replay.isPaused());
19+
});
20+
});

packages/ember/tests/dummy/app/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import config from './config/environment';
55
import * as Sentry from '@sentry/ember';
66

77
Sentry.init({
8+
replaysSessionSampleRate: 1,
9+
replaysOnErrorSampleRate: 1,
810
browserTracingOptions: {
911
_experiments: {
1012
// This lead to some flaky tests, as that is sometimes logged

packages/ember/tests/dummy/app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default class Router extends EmberRouter {
88

99
Router.map(function () {
1010
this.route('tracing');
11+
this.route('replay');
1112
this.route('slow-loading-route', function () {
1213
this.route('index', { path: '/' });
1314
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Route from '@ember/routing/route';
2+
import * as Sentry from '@sentry/ember';
3+
4+
export default class ReplayRoute extends Route {
5+
async beforeModel() {
6+
const { Replay } = Sentry;
7+
8+
if (!Sentry.getCurrentHub().getIntegration(Replay)) {
9+
const client = Sentry.getCurrentHub().getClient();
10+
client.addIntegration(new Replay());
11+
}
12+
}
13+
}

packages/ember/tests/dummy/app/templates/application.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<div class="nav">
1212
<Link @route='index'>Errors</Link>
1313
<Link @route='tracing'>Tracing</Link>
14+
<Link @route='replay'>Replay</Link>
1415
</div>
1516
<div class="content-container">
1617
{{outlet}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h2>Visiting this page starts Replay!</h2>

packages/ember/tests/dummy/config/environment.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ module.exports = function (environment) {
2222
ENV['@sentry/ember'] = {
2323
sentry: {
2424
tracesSampleRate: 1,
25-
dsn: process.env.SENTRY_DSN,
25+
// Include fake dsn so that instrumentation is enabled when running from cli
26+
dsn: process.env.SENTRY_DSN || 'https://[email protected]/0',
2627
browserTracingOptions: {
2728
tracingOrigins: ['localhost', 'doesntexist.example'],
2829
},
@@ -50,9 +51,6 @@ module.exports = function (environment) {
5051

5152
ENV.APP.rootElement = '#ember-testing';
5253
ENV.APP.autoboot = false;
53-
54-
// Include fake dsn so that instrumentation is enabled when running from cli
55-
ENV['@sentry/ember'].sentry.dsn = 'https://[email protected]/0';
5654
}
5755

5856
if (environment === 'production') {

0 commit comments

Comments
 (0)