@sentry/ember 10.53.0 → 10.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/addon/index.ts CHANGED
@@ -119,3 +119,8 @@ export const instrumentRoutePerformance = <T extends RouteConstructor>(BaseRoute
119
119
  };
120
120
 
121
121
  export * from '@sentry/browser';
122
+
123
+ /**
124
+ * Ember-specific browser tracing integration
125
+ */
126
+ export { browserTracingIntegration } from './utils/browserTracingIntegration';
@@ -1,40 +1,5 @@
1
- /* eslint-disable max-lines */
2
1
  import type ApplicationInstance from '@ember/application/instance';
3
- import { subscribe } from '@ember/instrumentation';
4
- import type Transition from '@ember/routing/-private/transition';
5
- import type RouterService from '@ember/routing/router-service';
6
- import { _backburner, run, scheduleOnce } from '@ember/runloop';
7
- import type { EmberRunQueues } from '@ember/runloop/-private/types';
8
- import { getOwnConfig, isTesting, macroCondition } from '@embroider/macros';
9
- import type {
10
- BrowserClient,
11
- startBrowserTracingNavigationSpan as startBrowserTracingNavigationSpanType,
12
- startBrowserTracingPageLoadSpan as startBrowserTracingPageLoadSpanType,
13
- } from '@sentry/browser';
14
- import {
15
- getActiveSpan,
16
- getClient,
17
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
18
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
19
- startInactiveSpan,
20
- } from '@sentry/browser';
21
- import type { Span } from '@sentry/core';
22
- import { addIntegration, browserPerformanceTimeOrigin, GLOBAL_OBJ, timestampInSeconds } from '@sentry/core';
23
- import type { ExtendedBackburner } from '@sentry/ember/runloop';
24
- import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig } from '../types';
25
-
26
- function getSentryConfig(): EmberSentryConfig {
27
- const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig;
28
- _global.__sentryEmberConfig = _global.__sentryEmberConfig ?? {};
29
- const environmentConfig = getOwnConfig<OwnConfig>().sentryConfig;
30
- if (!environmentConfig.sentry) {
31
- environmentConfig.sentry = {
32
- browserTracingOptions: {},
33
- };
34
- }
35
- Object.assign(environmentConfig.sentry, _global.__sentryEmberConfig);
36
- return environmentConfig;
37
- }
2
+ import { instrumentForPerformance, getSentryConfig } from '../utils/performance';
38
3
 
39
4
  export function initialize(appInstance: ApplicationInstance): void {
40
5
  // Disable in fastboot - we only want to run Sentry client-side
@@ -47,468 +12,12 @@ export function initialize(appInstance: ApplicationInstance): void {
47
12
  if (config['disablePerformance']) {
48
13
  return;
49
14
  }
50
- const performancePromise = instrumentForPerformance(appInstance);
51
- if (macroCondition(isTesting())) {
52
- (window as typeof window & { _sentryPerformanceLoad?: Promise<void> })._sentryPerformanceLoad = performancePromise;
53
- }
54
- }
55
-
56
- function getBackburner(): Pick<ExtendedBackburner, 'on' | 'off'> {
57
- if (_backburner) {
58
- return _backburner as unknown as Pick<ExtendedBackburner, 'on' | 'off'>;
59
- }
60
-
61
- if ((run as unknown as { backburner?: Pick<ExtendedBackburner, 'on' | 'off'> }).backburner) {
62
- return (run as unknown as { backburner: Pick<ExtendedBackburner, 'on' | 'off'> }).backburner;
63
- }
64
-
65
- return {
66
- on() {
67
- // noop
68
- },
69
- off() {
70
- // noop
71
- },
72
- };
73
- }
74
-
75
- function getTransitionInformation(
76
- transition: Transition | undefined,
77
- router: RouterService,
78
- ): { fromRoute?: string; toRoute?: string } {
79
- const fromRoute = transition?.from?.name;
80
- const toRoute = transition?.to?.name || router.currentRouteName;
81
- return {
82
- fromRoute,
83
- toRoute,
84
- };
85
- }
86
-
87
- // Only exported for testing
88
- export function _getLocationURL(location: EmberRouterMain['location']): string {
89
- if (!location?.getURL || !location?.formatURL) {
90
- return '';
91
- }
92
- const url = location.formatURL(location.getURL());
93
-
94
- // `implementation` is optional in Ember's predefined location types, so we also check if the URL starts with '#'.
95
- if (location.implementation === 'hash' || url.startsWith('#')) {
96
- return `${location.rootURL}${url}`;
97
- }
98
- return url;
99
- }
100
-
101
- export function _instrumentEmberRouter(
102
- routerService: RouterService,
103
- routerMain: EmberRouterMain,
104
- config: EmberSentryConfig,
105
- startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType,
106
- startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType,
107
- ): void {
108
- const { disableRunloopPerformance } = config;
109
- const location = routerMain.location;
110
- let activeRootSpan: Span | undefined;
111
- let transitionSpan: Span | undefined;
112
-
113
- // Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
114
- const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
115
- const url = _getLocationURL(location);
116
-
117
- const client = getClient<BrowserClient>();
118
-
119
- if (!client) {
120
- return;
121
- }
122
-
123
- if (url && browserTracingOptions.instrumentPageLoad !== false) {
124
- const routeInfo = routerService.recognize(url);
125
- activeRootSpan = startBrowserTracingPageLoadSpan(client, {
126
- name: `route:${routeInfo.name}`,
127
- attributes: {
128
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
129
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.ember',
130
- url,
131
- toRoute: routeInfo.name,
132
- },
133
- });
134
- }
135
-
136
- const finishActiveTransaction = (_: unknown, nextInstance: unknown): void => {
137
- if (nextInstance) {
138
- return;
139
- }
140
- activeRootSpan?.end();
141
- getBackburner().off('end', finishActiveTransaction);
142
- };
143
-
144
- if (browserTracingOptions.instrumentNavigation === false) {
145
- return;
146
- }
147
-
148
- routerService.on('routeWillChange', (transition: Transition) => {
149
- const { fromRoute, toRoute } = getTransitionInformation(transition, routerService);
150
-
151
- // We want to ignore loading && error routes
152
- if (transitionIsIntermediate(transition)) {
153
- return;
154
- }
155
-
156
- activeRootSpan?.end();
157
-
158
- activeRootSpan = startBrowserTracingNavigationSpan(client, {
159
- name: `route:${toRoute}`,
160
- attributes: {
161
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
162
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.ember',
163
- fromRoute,
164
- toRoute,
165
- },
166
- });
167
-
168
- transitionSpan = startInactiveSpan({
169
- attributes: {
170
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
171
- },
172
- op: 'ui.ember.transition',
173
- name: `route:${fromRoute} -> route:${toRoute}`,
174
- onlyIfParent: true,
175
- });
176
- });
177
-
178
- routerService.on('routeDidChange', transition => {
179
- if (!transitionSpan || !activeRootSpan || transitionIsIntermediate(transition)) {
180
- return;
181
- }
182
- transitionSpan.end();
183
-
184
- if (disableRunloopPerformance) {
185
- activeRootSpan.end();
186
- return;
187
- }
188
-
189
- getBackburner().on('end', finishActiveTransaction);
190
- });
191
- }
192
-
193
- function _instrumentEmberRunloop(config: EmberSentryConfig): void {
194
- const { disableRunloopPerformance, minimumRunloopQueueDuration } = config;
195
- if (disableRunloopPerformance) {
196
- return;
197
- }
198
-
199
- let currentQueueStart: number | undefined;
200
- let currentQueueSpan: Span | undefined;
201
- const instrumentedEmberQueues = [
202
- 'actions',
203
- 'routerTransitions',
204
- 'render',
205
- 'afterRender',
206
- 'destroy',
207
- ] as EmberRunQueues[];
208
-
209
- getBackburner().on('begin', (_: unknown, previousInstance: unknown) => {
210
- if (previousInstance) {
211
- return;
212
- }
213
- const activeSpan = getActiveSpan();
214
- if (!activeSpan) {
215
- return;
216
- }
217
- if (currentQueueSpan) {
218
- currentQueueSpan.end();
219
- }
220
- currentQueueStart = timestampInSeconds();
221
-
222
- const processQueue = (queue: EmberRunQueues): void => {
223
- // Process this queue using the end of the previous queue.
224
- if (currentQueueStart) {
225
- const now = timestampInSeconds();
226
- const minQueueDuration = minimumRunloopQueueDuration ?? 5;
227
-
228
- if ((now - currentQueueStart) * 1000 >= minQueueDuration) {
229
- startInactiveSpan({
230
- attributes: {
231
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
232
- },
233
- name: 'runloop',
234
- op: `ui.ember.runloop.${queue}`,
235
- startTime: currentQueueStart,
236
- onlyIfParent: true,
237
- })?.end(now);
238
- }
239
- currentQueueStart = undefined;
240
- }
241
-
242
- // Setup for next queue
243
15
 
244
- const stillActiveSpan = getActiveSpan();
245
- if (!stillActiveSpan) {
246
- return;
247
- }
248
- currentQueueStart = timestampInSeconds();
249
- };
250
-
251
- instrumentedEmberQueues.forEach(queue => {
252
- scheduleOnce(queue, null, processQueue, queue);
253
- });
254
- });
255
- getBackburner().on('end', (_: unknown, nextInstance: unknown) => {
256
- if (nextInstance) {
257
- return;
258
- }
259
- if (currentQueueSpan) {
260
- currentQueueSpan.end();
261
- currentQueueSpan = undefined;
262
- }
263
- });
264
- }
265
-
266
- type Payload = {
267
- containerKey: string;
268
- initialRender: true;
269
- object: string;
270
- };
271
-
272
- type RenderEntry = {
273
- payload: Payload;
274
- now: number;
275
- };
276
-
277
- interface RenderEntries {
278
- [name: string]: RenderEntry;
279
- }
280
-
281
- function processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries): void {
282
- const info = {
283
- payload,
284
- now: timestampInSeconds(),
285
- };
286
- beforeEntries[payload.object] = info;
287
- }
288
-
289
- function processComponentRenderAfter(
290
- payload: Payload,
291
- beforeEntries: RenderEntries,
292
- op: string,
293
- minComponentDuration: number,
294
- ): void {
295
- const begin = beforeEntries[payload.object];
296
-
297
- if (!begin) {
298
- return;
299
- }
300
-
301
- const now = timestampInSeconds();
302
- const componentRenderDuration = now - begin.now;
303
-
304
- if (componentRenderDuration * 1000 >= minComponentDuration) {
305
- startInactiveSpan({
306
- name: payload.containerKey || payload.object,
307
- op,
308
- startTime: begin.now,
309
- attributes: {
310
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
311
- },
312
- onlyIfParent: true,
313
- })?.end(now);
314
- }
315
- }
316
-
317
- function _instrumentComponents(config: EmberSentryConfig): void {
318
- const { disableInstrumentComponents, minimumComponentRenderDuration, enableComponentDefinitions } = config;
319
- if (disableInstrumentComponents) {
320
- return;
321
- }
322
-
323
- const minComponentDuration = minimumComponentRenderDuration ?? 2;
324
-
325
- const beforeEntries = {} as RenderEntries;
326
- const beforeComponentDefinitionEntries = {} as RenderEntries;
327
-
328
- function _subscribeToRenderEvents(): void {
329
- subscribe('render.component', {
330
- before(_name: string, _timestamp: number, payload: Payload) {
331
- processComponentRenderBefore(payload, beforeEntries);
332
- },
333
-
334
- after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
335
- processComponentRenderAfter(payload, beforeEntries, 'ui.ember.component.render', minComponentDuration);
336
- },
337
- });
338
- if (enableComponentDefinitions) {
339
- subscribe('render.getComponentDefinition', {
340
- before(_name: string, _timestamp: number, payload: Payload) {
341
- processComponentRenderBefore(payload, beforeComponentDefinitionEntries);
342
- },
343
-
344
- after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
345
- processComponentRenderAfter(payload, beforeComponentDefinitionEntries, 'ui.ember.component.definition', 0);
346
- },
347
- });
348
- }
349
- }
350
- _subscribeToRenderEvents();
351
- }
352
-
353
- function _instrumentInitialLoad(config: EmberSentryConfig): void {
354
- const startName = '@sentry/ember:initial-load-start';
355
- const endName = '@sentry/ember:initial-load-end';
356
-
357
- const { HAS_PERFORMANCE, HAS_PERFORMANCE_TIMING } = _hasPerformanceSupport();
358
-
359
- if (!HAS_PERFORMANCE) {
360
- return;
361
- }
362
-
363
- const { performance } = window;
364
-
365
- if (config.disableInitialLoadInstrumentation) {
366
- performance.clearMarks(startName);
367
- performance.clearMarks(endName);
368
- return;
369
- }
370
-
371
- const origin = browserPerformanceTimeOrigin();
372
- // Split performance check in two so clearMarks still happens even if timeOrigin isn't available.
373
- if (!HAS_PERFORMANCE_TIMING || origin === undefined) {
374
- return;
375
- }
376
- const measureName = '@sentry/ember:initial-load';
377
-
378
- const startMarkExists = performance.getEntriesByName(startName).length > 0;
379
- const endMarkExists = performance.getEntriesByName(endName).length > 0;
380
- if (!startMarkExists || !endMarkExists) {
381
- return;
382
- }
383
-
384
- performance.measure(measureName, startName, endName);
385
- const measures = performance.getEntriesByName(measureName);
386
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
387
- const measure = measures[0]!;
388
-
389
- const startTime = (measure.startTime + origin) / 1000;
390
- const endTime = startTime + measure.duration / 1000;
391
-
392
- startInactiveSpan({
393
- op: 'ui.ember.init',
394
- name: 'init',
395
- attributes: {
396
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
397
- },
398
- startTime,
399
- onlyIfParent: true,
400
- })?.end(endTime);
401
- performance.clearMarks(startName);
402
- performance.clearMarks(endName);
403
-
404
- performance.clearMeasures(measureName);
405
- }
406
-
407
- function _hasPerformanceSupport(): { HAS_PERFORMANCE: boolean; HAS_PERFORMANCE_TIMING: boolean } {
408
- // TS says that all of these methods are always available, but some of them may not be supported in older browsers
409
- // So we "pretend" they are all optional in order to be able to check this properly without TS complaining
410
- const _performance = window.performance as {
411
- clearMarks?: Performance['clearMarks'];
412
- clearMeasures?: Performance['clearMeasures'];
413
- measure?: Performance['measure'];
414
- getEntriesByName?: Performance['getEntriesByName'];
415
- };
416
- const HAS_PERFORMANCE = Boolean(_performance?.clearMarks && _performance.clearMeasures);
417
- const HAS_PERFORMANCE_TIMING = Boolean(
418
- _performance.measure && _performance.getEntriesByName && browserPerformanceTimeOrigin !== undefined,
419
- );
420
-
421
- return {
422
- HAS_PERFORMANCE,
423
- HAS_PERFORMANCE_TIMING,
424
- };
425
- }
426
-
427
- export async function instrumentForPerformance(appInstance: ApplicationInstance): Promise<void> {
428
- const config = getSentryConfig();
429
- // Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
430
- const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
431
-
432
- const { browserTracingIntegration, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan } =
433
- await import('@sentry/browser');
434
-
435
- const idleTimeout = config.transitionTimeout || 5000;
436
-
437
- const browserTracing = browserTracingIntegration({
438
- idleTimeout,
439
- ...browserTracingOptions,
440
- instrumentNavigation: false,
441
- instrumentPageLoad: false,
442
- });
443
-
444
- const client = getClient<BrowserClient>();
445
- const isAlreadyInitialized = macroCondition(isTesting()) ? !!client?.getIntegrationByName('BrowserTracing') : false;
446
- addIntegration(browserTracing);
447
-
448
- // We _always_ call this, as it triggers the page load & navigation spans
449
- _instrumentNavigation(appInstance, config, startBrowserTracingPageLoadSpan, startBrowserTracingNavigationSpan);
450
-
451
- // Skip instrumenting the stuff below again in tests, as these are not reset between tests
452
- if (isAlreadyInitialized) {
453
- return;
454
- }
455
-
456
- _instrumentEmberRunloop(config);
457
- _instrumentComponents(config);
458
- _instrumentInitialLoad(config);
459
- }
460
-
461
- function _instrumentNavigation(
462
- appInstance: ApplicationInstance,
463
- config: EmberSentryConfig,
464
- startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType,
465
- startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType,
466
- ): void {
467
- // eslint-disable-next-line ember/no-private-routing-service
468
- const routerMain = appInstance.lookup('router:main') as EmberRouterMain;
469
- let routerService = appInstance.lookup('service:router') as RouterService & {
470
- externalRouter?: RouterService;
471
- _hasMountedSentryPerformanceRouting?: boolean;
472
- };
473
-
474
- if (routerService.externalRouter) {
475
- // Using ember-engines-router-service in an engine.
476
- routerService = routerService.externalRouter;
477
- }
478
- if (routerService._hasMountedSentryPerformanceRouting) {
479
- // Routing listens to route changes on the main router, and should not be initialized multiple times per page.
480
- return;
481
- }
482
- if (!routerService.recognize) {
483
- // Router is missing critical functionality to limit cardinality of the transaction names.
484
- return;
485
- }
486
-
487
- routerService._hasMountedSentryPerformanceRouting = true;
488
- _instrumentEmberRouter(
489
- routerService,
490
- routerMain,
491
- config,
492
- startBrowserTracingPageLoadSpan,
493
- startBrowserTracingNavigationSpan,
494
- );
16
+ // Run this in the next tick to ensure the ember router etc. is properly initialized
17
+ instrumentForPerformance(appInstance);
495
18
  }
496
19
 
497
20
  export default {
498
21
  initialize,
22
+ name: 'sentry-performance',
499
23
  };
500
-
501
- function transitionIsIntermediate(transition: Transition): boolean {
502
- // We want to use ignore, as this may actually be defined on new versions
503
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
504
- // @ts-ignore This actually exists on newer versions
505
- const isIntermediate: boolean | undefined = transition.isIntermediate;
506
-
507
- if (typeof isIntermediate === 'boolean') {
508
- return isIntermediate;
509
- }
510
-
511
- // For versions without this, we look if the route is a `.loading` or `.error` route
512
- // This is not perfect and may false-positive in some cases, but it's the best we can do
513
- return transition.to?.localName === 'loading' || transition.to?.localName === 'error';
514
- }
@@ -0,0 +1,87 @@
1
+ import {
2
+ browserTracingIntegration as originalBrowserTracingIntegration,
3
+ startBrowserTracingNavigationSpan,
4
+ startBrowserTracingPageLoadSpan,
5
+ } from '@sentry/browser';
6
+ import { consoleSandbox, type Integration } from '@sentry/core';
7
+ import type ApplicationInstance from '@ember/application/instance';
8
+ import { instrumentEmberAppInstanceForPerformance } from './instrumentEmberAppInstanceForPerformance';
9
+ import { instrumentGlobalsForPerformance } from './instrumentEmberGlobals';
10
+ import { isTesting, macroCondition } from '@embroider/macros';
11
+
12
+ type EmberBrowserTracingIntegrationOptions = Parameters<typeof originalBrowserTracingIntegration>[0] & {
13
+ // TODO(v11): make this required
14
+ appInstance?: ApplicationInstance;
15
+ disableRunloopPerformance?: boolean;
16
+ minimumRunloopQueueDuration?: number;
17
+ disableInstrumentComponents?: boolean;
18
+ minimumComponentRenderDuration?: number;
19
+ enableComponentDefinitions?: boolean;
20
+ disableInitialLoadInstrumentation?: boolean;
21
+ };
22
+
23
+ let _initialized = false;
24
+
25
+ export function browserTracingIntegration(options: EmberBrowserTracingIntegrationOptions): Integration {
26
+ const { appInstance } = options;
27
+
28
+ const instrumentNavigation = options.instrumentNavigation ?? true;
29
+ const instrumentPageLoad = options.instrumentPageLoad ?? true;
30
+
31
+ const integration = originalBrowserTracingIntegration({
32
+ ...options,
33
+ instrumentNavigation: false,
34
+ instrumentPageLoad: false,
35
+ });
36
+
37
+ const appInstancePerformanceConfig = {
38
+ disableRunloopPerformance: options.disableRunloopPerformance ?? false,
39
+ instrumentPageLoad,
40
+ instrumentNavigation,
41
+ };
42
+
43
+ const globalsPerformanceConfig = {
44
+ disableRunloopPerformance: options.disableRunloopPerformance ?? false,
45
+ minimumRunloopQueueDuration: options.minimumRunloopQueueDuration,
46
+ disableInstrumentComponents: options.disableInstrumentComponents ?? false,
47
+ minimumComponentRenderDuration: options.minimumComponentRenderDuration,
48
+ enableComponentDefinitions: options.enableComponentDefinitions ?? false,
49
+ disableInitialLoadInstrumentation: options.disableInitialLoadInstrumentation ?? false,
50
+ };
51
+
52
+ return {
53
+ ...integration,
54
+ afterAllSetup(client) {
55
+ integration.afterAllSetup(client);
56
+
57
+ // Run this in the next tick to ensure the ember router etc. is properly initialized
58
+
59
+ setTimeout(() => {
60
+ if (appInstance) {
61
+ instrumentEmberAppInstanceForPerformance(
62
+ client,
63
+ appInstance,
64
+ appInstancePerformanceConfig,
65
+ startBrowserTracingPageLoadSpan,
66
+ startBrowserTracingNavigationSpan,
67
+ );
68
+ } else {
69
+ consoleSandbox(() => {
70
+ // eslint-disable-next-line no-console
71
+ console.warn('Skipping router instrumentation because appInstance is not provided.');
72
+ });
73
+ }
74
+
75
+ // We only want to run this once in tests!
76
+ if (macroCondition(isTesting())) {
77
+ if (_initialized) {
78
+ return;
79
+ }
80
+ }
81
+
82
+ instrumentGlobalsForPerformance(globalsPerformanceConfig);
83
+ _initialized = true;
84
+ });
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,175 @@
1
+ import type ApplicationInstance from '@ember/application/instance';
2
+ import type Transition from '@ember/routing/-private/transition';
3
+ import type RouterService from '@ember/routing/router-service';
4
+ import type {
5
+ startBrowserTracingNavigationSpan as startBrowserTracingNavigationSpanType,
6
+ startBrowserTracingPageLoadSpan as startBrowserTracingPageLoadSpanType,
7
+ } from '@sentry/browser';
8
+ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startInactiveSpan } from '@sentry/browser';
9
+ import type { Client, Span } from '@sentry/core';
10
+ import type { EmberRouterMain } from '../types';
11
+ import { getBackburner } from './performance';
12
+
13
+ export function instrumentEmberAppInstanceForPerformance(
14
+ client: Client,
15
+ appInstance: ApplicationInstance,
16
+ config: { disableRunloopPerformance?: boolean; instrumentPageLoad?: boolean; instrumentNavigation?: boolean },
17
+ startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType,
18
+ startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType,
19
+ ): void {
20
+ // eslint-disable-next-line ember/no-private-routing-service
21
+ const routerMain = appInstance.lookup('router:main') as EmberRouterMain;
22
+ let routerService = appInstance.lookup('service:router') as RouterService & {
23
+ externalRouter?: RouterService;
24
+ _hasMountedSentryPerformanceRouting?: boolean;
25
+ };
26
+
27
+ if (routerService.externalRouter) {
28
+ // Using ember-engines-router-service in an engine.
29
+ routerService = routerService.externalRouter;
30
+ }
31
+ if (routerService._hasMountedSentryPerformanceRouting) {
32
+ // Routing listens to route changes on the main router, and should not be initialized multiple times per page.
33
+ return;
34
+ }
35
+ if (!routerService.recognize) {
36
+ // Router is missing critical functionality to limit cardinality of the transaction names.
37
+ return;
38
+ }
39
+
40
+ routerService._hasMountedSentryPerformanceRouting = true;
41
+ _instrumentEmberRouter(
42
+ client,
43
+ routerService,
44
+ routerMain,
45
+ config,
46
+ startBrowserTracingPageLoadSpan,
47
+ startBrowserTracingNavigationSpan,
48
+ );
49
+ }
50
+
51
+ function getTransitionInformation(
52
+ transition: Transition | undefined,
53
+ router: RouterService,
54
+ ): { fromRoute?: string; toRoute?: string } {
55
+ const fromRoute = transition?.from?.name;
56
+ const toRoute = transition?.to?.name || router.currentRouteName;
57
+ return {
58
+ fromRoute,
59
+ toRoute,
60
+ };
61
+ }
62
+
63
+ // Only exported for testing
64
+ export function _getLocationURL(location: EmberRouterMain['location']): string {
65
+ if (!location?.getURL || !location?.formatURL) {
66
+ return '';
67
+ }
68
+ const url = location.formatURL(location.getURL());
69
+
70
+ // `implementation` is optional in Ember's predefined location types, so we also check if the URL starts with '#'.
71
+ if (location.implementation === 'hash' || url.startsWith('#')) {
72
+ return `${location.rootURL}${url}`;
73
+ }
74
+ return url;
75
+ }
76
+
77
+ function _instrumentEmberRouter(
78
+ client: Client,
79
+ routerService: RouterService,
80
+ routerMain: EmberRouterMain,
81
+ config: { disableRunloopPerformance?: boolean; instrumentPageLoad?: boolean; instrumentNavigation?: boolean },
82
+ startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType,
83
+ startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType,
84
+ ): void {
85
+ const { disableRunloopPerformance, instrumentPageLoad, instrumentNavigation } = config;
86
+ const location = routerMain.location;
87
+ let activeRootSpan: Span | undefined;
88
+ let transitionSpan: Span | undefined;
89
+
90
+ const url = _getLocationURL(location);
91
+
92
+ if (url && instrumentPageLoad !== false) {
93
+ const routeInfo = routerService.recognize(url);
94
+ activeRootSpan = startBrowserTracingPageLoadSpan(client, {
95
+ name: `route:${routeInfo.name}`,
96
+ attributes: {
97
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
98
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.ember',
99
+ url,
100
+ toRoute: routeInfo.name,
101
+ },
102
+ });
103
+ }
104
+
105
+ const finishActiveTransaction = (_: unknown, nextInstance: unknown): void => {
106
+ if (nextInstance) {
107
+ return;
108
+ }
109
+ activeRootSpan?.end();
110
+ getBackburner().off('end', finishActiveTransaction);
111
+ };
112
+
113
+ if (instrumentNavigation === false) {
114
+ return;
115
+ }
116
+
117
+ routerService.on('routeWillChange', (transition: Transition) => {
118
+ const { fromRoute, toRoute } = getTransitionInformation(transition, routerService);
119
+
120
+ // We want to ignore loading && error routes
121
+ if (transitionIsIntermediate(transition)) {
122
+ return;
123
+ }
124
+
125
+ activeRootSpan?.end();
126
+
127
+ activeRootSpan = startBrowserTracingNavigationSpan(client, {
128
+ name: `route:${toRoute}`,
129
+ attributes: {
130
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
131
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.ember',
132
+ fromRoute,
133
+ toRoute,
134
+ },
135
+ });
136
+
137
+ transitionSpan = startInactiveSpan({
138
+ attributes: {
139
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
140
+ },
141
+ op: 'ui.ember.transition',
142
+ name: `route:${fromRoute} -> route:${toRoute}`,
143
+ onlyIfParent: true,
144
+ });
145
+ });
146
+
147
+ routerService.on('routeDidChange', transition => {
148
+ if (!transitionSpan || !activeRootSpan || transitionIsIntermediate(transition)) {
149
+ return;
150
+ }
151
+ transitionSpan.end();
152
+
153
+ if (disableRunloopPerformance) {
154
+ activeRootSpan.end();
155
+ return;
156
+ }
157
+
158
+ getBackburner().on('end', finishActiveTransaction);
159
+ });
160
+ }
161
+
162
+ function transitionIsIntermediate(transition: Transition): boolean {
163
+ // We want to use ignore, as this may actually be defined on new versions
164
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
165
+ // @ts-ignore This actually exists on newer versions
166
+ const isIntermediate: boolean | undefined = transition.isIntermediate;
167
+
168
+ if (typeof isIntermediate === 'boolean') {
169
+ return isIntermediate;
170
+ }
171
+
172
+ // For versions without this, we look if the route is a `.loading` or `.error` route
173
+ // This is not perfect and may false-positive in some cases, but it's the best we can do
174
+ return transition.to?.localName === 'loading' || transition.to?.localName === 'error';
175
+ }
@@ -0,0 +1,265 @@
1
+ import { subscribe } from '@ember/instrumentation';
2
+ import { scheduleOnce } from '@ember/runloop';
3
+ import type { EmberRunQueues } from '@ember/runloop/-private/types';
4
+ import { getActiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/browser';
5
+ import type { Span } from '@sentry/core';
6
+ import { browserPerformanceTimeOrigin, timestampInSeconds } from '@sentry/core';
7
+ import { getBackburner } from './performance';
8
+
9
+ type Payload = {
10
+ containerKey: string;
11
+ initialRender: true;
12
+ object: string;
13
+ };
14
+
15
+ type RenderEntry = {
16
+ payload: Payload;
17
+ now: number;
18
+ };
19
+
20
+ interface RenderEntries {
21
+ [name: string]: RenderEntry;
22
+ }
23
+
24
+ /** This is global, so should only be run once in tests! */
25
+ export function instrumentGlobalsForPerformance(config: {
26
+ disableRunloopPerformance?: boolean;
27
+ minimumRunloopQueueDuration?: number;
28
+ disableInstrumentComponents?: boolean;
29
+ minimumComponentRenderDuration?: number;
30
+ enableComponentDefinitions?: boolean;
31
+ disableInitialLoadInstrumentation?: boolean;
32
+ }): void {
33
+ const {
34
+ disableRunloopPerformance,
35
+ minimumRunloopQueueDuration,
36
+ disableInstrumentComponents,
37
+ minimumComponentRenderDuration,
38
+ enableComponentDefinitions,
39
+ disableInitialLoadInstrumentation,
40
+ } = config;
41
+
42
+ if (!disableRunloopPerformance) {
43
+ _instrumentEmberRunloop({
44
+ minimumRunloopQueueDuration,
45
+ });
46
+ }
47
+ if (!disableInstrumentComponents) {
48
+ _instrumentComponents({
49
+ minimumComponentRenderDuration,
50
+ enableComponentDefinitions,
51
+ });
52
+ }
53
+ if (!disableInitialLoadInstrumentation) {
54
+ _instrumentInitialLoad();
55
+ }
56
+ }
57
+
58
+ function _instrumentEmberRunloop(config: { minimumRunloopQueueDuration?: number }): void {
59
+ const { minimumRunloopQueueDuration } = config;
60
+ let currentQueueStart: number | undefined;
61
+ let currentQueueSpan: Span | undefined;
62
+ const instrumentedEmberQueues = [
63
+ 'actions',
64
+ 'routerTransitions',
65
+ 'render',
66
+ 'afterRender',
67
+ 'destroy',
68
+ ] as EmberRunQueues[];
69
+
70
+ getBackburner().on('begin', (_: unknown, previousInstance: unknown) => {
71
+ if (previousInstance) {
72
+ return;
73
+ }
74
+ const activeSpan = getActiveSpan();
75
+ if (!activeSpan) {
76
+ return;
77
+ }
78
+ if (currentQueueSpan) {
79
+ currentQueueSpan.end();
80
+ }
81
+ currentQueueStart = timestampInSeconds();
82
+
83
+ const processQueue = (queue: EmberRunQueues): void => {
84
+ // Process this queue using the end of the previous queue.
85
+ if (currentQueueStart) {
86
+ const now = timestampInSeconds();
87
+ const minQueueDuration = minimumRunloopQueueDuration ?? 5;
88
+
89
+ if ((now - currentQueueStart) * 1000 >= minQueueDuration) {
90
+ startInactiveSpan({
91
+ attributes: {
92
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
93
+ },
94
+ name: 'runloop',
95
+ op: `ui.ember.runloop.${queue}`,
96
+ startTime: currentQueueStart,
97
+ onlyIfParent: true,
98
+ })?.end(now);
99
+ }
100
+ currentQueueStart = undefined;
101
+ }
102
+
103
+ // Setup for next queue
104
+
105
+ const stillActiveSpan = getActiveSpan();
106
+ if (!stillActiveSpan) {
107
+ return;
108
+ }
109
+ currentQueueStart = timestampInSeconds();
110
+ };
111
+
112
+ instrumentedEmberQueues.forEach(queue => {
113
+ scheduleOnce(queue, null, processQueue, queue);
114
+ });
115
+ });
116
+ getBackburner().on('end', (_: unknown, nextInstance: unknown) => {
117
+ if (nextInstance) {
118
+ return;
119
+ }
120
+ if (currentQueueSpan) {
121
+ currentQueueSpan.end();
122
+ currentQueueSpan = undefined;
123
+ }
124
+ });
125
+ }
126
+
127
+ function processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries): void {
128
+ const info = {
129
+ payload,
130
+ now: timestampInSeconds(),
131
+ };
132
+ beforeEntries[payload.object] = info;
133
+ }
134
+
135
+ function processComponentRenderAfter(
136
+ payload: Payload,
137
+ beforeEntries: RenderEntries,
138
+ op: string,
139
+ minComponentDuration: number,
140
+ ): void {
141
+ const begin = beforeEntries[payload.object];
142
+
143
+ if (!begin) {
144
+ return;
145
+ }
146
+
147
+ const now = timestampInSeconds();
148
+ const componentRenderDuration = now - begin.now;
149
+
150
+ if (componentRenderDuration * 1000 >= minComponentDuration) {
151
+ startInactiveSpan({
152
+ name: payload.containerKey || payload.object,
153
+ op,
154
+ startTime: begin.now,
155
+ attributes: {
156
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
157
+ },
158
+ onlyIfParent: true,
159
+ })?.end(now);
160
+ }
161
+ }
162
+
163
+ function _instrumentComponents(config: {
164
+ minimumComponentRenderDuration?: number;
165
+ enableComponentDefinitions?: boolean;
166
+ }): void {
167
+ const { minimumComponentRenderDuration, enableComponentDefinitions } = config;
168
+
169
+ const minComponentDuration = minimumComponentRenderDuration ?? 2;
170
+
171
+ const beforeEntries = {} as RenderEntries;
172
+ const beforeComponentDefinitionEntries = {} as RenderEntries;
173
+
174
+ function _subscribeToRenderEvents(): void {
175
+ subscribe('render.component', {
176
+ before(_name: string, _timestamp: number, payload: Payload) {
177
+ processComponentRenderBefore(payload, beforeEntries);
178
+ },
179
+
180
+ after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
181
+ processComponentRenderAfter(payload, beforeEntries, 'ui.ember.component.render', minComponentDuration);
182
+ },
183
+ });
184
+ if (enableComponentDefinitions) {
185
+ subscribe('render.getComponentDefinition', {
186
+ before(_name: string, _timestamp: number, payload: Payload) {
187
+ processComponentRenderBefore(payload, beforeComponentDefinitionEntries);
188
+ },
189
+
190
+ after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
191
+ processComponentRenderAfter(payload, beforeComponentDefinitionEntries, 'ui.ember.component.definition', 0);
192
+ },
193
+ });
194
+ }
195
+ }
196
+ _subscribeToRenderEvents();
197
+ }
198
+
199
+ function _instrumentInitialLoad(): void {
200
+ const startName = '@sentry/ember:initial-load-start';
201
+ const endName = '@sentry/ember:initial-load-end';
202
+
203
+ const { HAS_PERFORMANCE, HAS_PERFORMANCE_TIMING } = _hasPerformanceSupport();
204
+
205
+ if (!HAS_PERFORMANCE) {
206
+ return;
207
+ }
208
+
209
+ const { performance } = window;
210
+
211
+ const origin = browserPerformanceTimeOrigin();
212
+ // Split performance check in two so clearMarks still happens even if timeOrigin isn't available.
213
+ if (!HAS_PERFORMANCE_TIMING || origin === undefined) {
214
+ return;
215
+ }
216
+ const measureName = '@sentry/ember:initial-load';
217
+
218
+ const startMarkExists = performance.getEntriesByName(startName).length > 0;
219
+ const endMarkExists = performance.getEntriesByName(endName).length > 0;
220
+ if (!startMarkExists || !endMarkExists) {
221
+ return;
222
+ }
223
+
224
+ performance.measure(measureName, startName, endName);
225
+ const measures = performance.getEntriesByName(measureName);
226
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227
+ const measure = measures[0]!;
228
+
229
+ const startTime = (measure.startTime + origin) / 1000;
230
+ const endTime = startTime + measure.duration / 1000;
231
+
232
+ startInactiveSpan({
233
+ op: 'ui.ember.init',
234
+ name: 'init',
235
+ attributes: {
236
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember',
237
+ },
238
+ startTime,
239
+ onlyIfParent: true,
240
+ })?.end(endTime);
241
+ performance.clearMarks(startName);
242
+ performance.clearMarks(endName);
243
+
244
+ performance.clearMeasures(measureName);
245
+ }
246
+
247
+ function _hasPerformanceSupport(): { HAS_PERFORMANCE: boolean; HAS_PERFORMANCE_TIMING: boolean } {
248
+ // TS says that all of these methods are always available, but some of them may not be supported in older browsers
249
+ // So we "pretend" they are all optional in order to be able to check this properly without TS complaining
250
+ const _performance = window.performance as {
251
+ clearMarks?: Performance['clearMarks'];
252
+ clearMeasures?: Performance['clearMeasures'];
253
+ measure?: Performance['measure'];
254
+ getEntriesByName?: Performance['getEntriesByName'];
255
+ };
256
+ const HAS_PERFORMANCE = Boolean(_performance?.clearMarks && _performance.clearMeasures);
257
+ const HAS_PERFORMANCE_TIMING = Boolean(
258
+ _performance.measure && _performance.getEntriesByName && browserPerformanceTimeOrigin !== undefined,
259
+ );
260
+
261
+ return {
262
+ HAS_PERFORMANCE,
263
+ HAS_PERFORMANCE_TIMING,
264
+ };
265
+ }
@@ -0,0 +1,80 @@
1
+ import type ApplicationInstance from '@ember/application/instance';
2
+ import { _backburner, run } from '@ember/runloop';
3
+ import { getOwnConfig, importSync, isTesting, macroCondition } from '@embroider/macros';
4
+ import { getClient } from '@sentry/browser';
5
+ import { addIntegration, GLOBAL_OBJ } from '@sentry/core';
6
+ import type { ExtendedBackburner } from '@sentry/ember/runloop';
7
+ import type { EmberSentryConfig, GlobalConfig, OwnConfig } from '../types';
8
+ import type { browserTracingIntegration as browserTracingIntegrationType } from './browserTracingIntegration';
9
+
10
+ export function getSentryConfig(): EmberSentryConfig {
11
+ const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig;
12
+ _global.__sentryEmberConfig = _global.__sentryEmberConfig ?? {};
13
+ const environmentConfig = getOwnConfig<OwnConfig>().sentryConfig;
14
+ if (!environmentConfig.sentry) {
15
+ environmentConfig.sentry = {
16
+ browserTracingOptions: {},
17
+ };
18
+ }
19
+ Object.assign(environmentConfig.sentry, _global.__sentryEmberConfig);
20
+ return environmentConfig;
21
+ }
22
+
23
+ export function getBackburner(): Pick<ExtendedBackburner, 'on' | 'off'> {
24
+ if (_backburner) {
25
+ return _backburner as unknown as Pick<ExtendedBackburner, 'on' | 'off'>;
26
+ }
27
+
28
+ if ((run as unknown as { backburner?: Pick<ExtendedBackburner, 'on' | 'off'> }).backburner) {
29
+ return (run as unknown as { backburner: Pick<ExtendedBackburner, 'on' | 'off'> }).backburner;
30
+ }
31
+
32
+ return {
33
+ on() {
34
+ // noop
35
+ },
36
+ off() {
37
+ // noop
38
+ },
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Utility to register the browser tracing integration and instrument the app instance for performance.
44
+ */
45
+ export function instrumentForPerformance(appInstance: ApplicationInstance): void {
46
+ const config = getSentryConfig();
47
+ // Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
48
+ const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
49
+
50
+ const { browserTracingIntegration } = importSync('./browserTracingIntegration') as {
51
+ browserTracingIntegration: typeof browserTracingIntegrationType;
52
+ };
53
+
54
+ const idleTimeout = config.transitionTimeout || 5000;
55
+
56
+ const emberSpecificConfig = {
57
+ minimumRunloopQueueDuration: config.minimumRunloopQueueDuration,
58
+ minimumComponentRenderDuration: config.minimumComponentRenderDuration,
59
+ enableComponentDefinitions: config.enableComponentDefinitions,
60
+ disableInitialLoadInstrumentation: config.disableInitialLoadInstrumentation,
61
+ disableRunloopPerformance: config.disableRunloopPerformance,
62
+ disableInstrumentComponents: config.disableInstrumentComponents,
63
+ };
64
+
65
+ const browserTracing = browserTracingIntegration({
66
+ appInstance,
67
+ idleTimeout,
68
+ ...browserTracingOptions,
69
+ ...emberSpecificConfig,
70
+ });
71
+
72
+ const client = getClient();
73
+ const isAlreadyInitialized = macroCondition(isTesting()) ? client?.getIntegrationByName('BrowserTracing') : false;
74
+ addIntegration(browserTracing);
75
+
76
+ // Ensure this is re-run in tests even if the integration is already initialized
77
+ if (isAlreadyInitialized && client) {
78
+ browserTracing.afterAllSetup?.(client);
79
+ }
80
+ }
package/index.d.ts CHANGED
@@ -8,3 +8,7 @@ export declare function init(_runtimeConfig?: BrowserOptions): Client | undefine
8
8
  type RouteConstructor = new (...args: ConstructorParameters<typeof Route>) => Route;
9
9
  export declare const instrumentRoutePerformance: <T extends RouteConstructor>(BaseRoute: T) => T;
10
10
  export * from '@sentry/browser';
11
+ /**
12
+ * Ember-specific browser tracing integration
13
+ */
14
+ export { browserTracingIntegration } from './utils/browserTracingIntegration';
@@ -1,12 +1,7 @@
1
1
  import type ApplicationInstance from '@ember/application/instance';
2
- import type RouterService from '@ember/routing/router-service';
3
- import type { startBrowserTracingNavigationSpan as startBrowserTracingNavigationSpanType, startBrowserTracingPageLoadSpan as startBrowserTracingPageLoadSpanType } from '@sentry/browser';
4
- import type { EmberRouterMain, EmberSentryConfig } from '../types';
5
2
  export declare function initialize(appInstance: ApplicationInstance): void;
6
- export declare function _getLocationURL(location: EmberRouterMain['location']): string;
7
- export declare function _instrumentEmberRouter(routerService: RouterService, routerMain: EmberRouterMain, config: EmberSentryConfig, startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType, startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType): void;
8
- export declare function instrumentForPerformance(appInstance: ApplicationInstance): Promise<void>;
9
3
  declare const _default: {
10
4
  initialize: typeof initialize;
5
+ name: string;
11
6
  };
12
7
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/ember",
3
- "version": "10.53.0",
3
+ "version": "10.54.0",
4
4
  "description": "Official Sentry SDK for Ember.js",
5
5
  "repository": "git://github.com/getsentry/sentry-javascript.git",
6
6
  "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember",
@@ -32,8 +32,8 @@
32
32
  "dependencies": {
33
33
  "@babel/core": "^7.27.7",
34
34
  "@embroider/macros": "^1.16.0",
35
- "@sentry/browser": "10.53.0",
36
- "@sentry/core": "10.53.0",
35
+ "@sentry/browser": "10.54.0",
36
+ "@sentry/core": "10.54.0",
37
37
  "ember-auto-import": "^2.7.2",
38
38
  "ember-cli-babel": "^8.2.0",
39
39
  "ember-cli-htmlbars": "^6.1.1",
@@ -0,0 +1,14 @@
1
+ import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser';
2
+ import { type Integration } from '@sentry/core';
3
+ import type ApplicationInstance from '@ember/application/instance';
4
+ type EmberBrowserTracingIntegrationOptions = Parameters<typeof originalBrowserTracingIntegration>[0] & {
5
+ appInstance?: ApplicationInstance;
6
+ disableRunloopPerformance?: boolean;
7
+ minimumRunloopQueueDuration?: number;
8
+ disableInstrumentComponents?: boolean;
9
+ minimumComponentRenderDuration?: number;
10
+ enableComponentDefinitions?: boolean;
11
+ disableInitialLoadInstrumentation?: boolean;
12
+ };
13
+ export declare function browserTracingIntegration(options: EmberBrowserTracingIntegrationOptions): Integration;
14
+ export {};
@@ -0,0 +1,10 @@
1
+ import type ApplicationInstance from '@ember/application/instance';
2
+ import type { startBrowserTracingNavigationSpan as startBrowserTracingNavigationSpanType, startBrowserTracingPageLoadSpan as startBrowserTracingPageLoadSpanType } from '@sentry/browser';
3
+ import type { Client } from '@sentry/core';
4
+ import type { EmberRouterMain } from '../types';
5
+ export declare function instrumentEmberAppInstanceForPerformance(client: Client, appInstance: ApplicationInstance, config: {
6
+ disableRunloopPerformance?: boolean;
7
+ instrumentPageLoad?: boolean;
8
+ instrumentNavigation?: boolean;
9
+ }, startBrowserTracingPageLoadSpan: typeof startBrowserTracingPageLoadSpanType, startBrowserTracingNavigationSpan: typeof startBrowserTracingNavigationSpanType): void;
10
+ export declare function _getLocationURL(location: EmberRouterMain['location']): string;
@@ -0,0 +1,9 @@
1
+ /** This is global, so should only be run once in tests! */
2
+ export declare function instrumentGlobalsForPerformance(config: {
3
+ disableRunloopPerformance?: boolean;
4
+ minimumRunloopQueueDuration?: number;
5
+ disableInstrumentComponents?: boolean;
6
+ minimumComponentRenderDuration?: number;
7
+ enableComponentDefinitions?: boolean;
8
+ disableInitialLoadInstrumentation?: boolean;
9
+ }): void;
@@ -0,0 +1,9 @@
1
+ import type ApplicationInstance from '@ember/application/instance';
2
+ import type { ExtendedBackburner } from '@sentry/ember/runloop';
3
+ import type { EmberSentryConfig } from '../types';
4
+ export declare function getSentryConfig(): EmberSentryConfig;
5
+ export declare function getBackburner(): Pick<ExtendedBackburner, 'on' | 'off'>;
6
+ /**
7
+ * Utility to register the browser tracing integration and instrument the app instance for performance.
8
+ */
9
+ export declare function instrumentForPerformance(appInstance: ApplicationInstance): void;