Skip to content

Commit 279ebcd

Browse files
committed
feat: Add router instrumentation for @sentry/ember
This will add routing instrumentation for Ember transitions to @sentry/ember. The import will be dynamically loading depending on configuration settings with to not increase bundle size.
1 parent 9428c9a commit 279ebcd

File tree

16 files changed

+241
-116
lines changed

16 files changed

+241
-116
lines changed

packages/ember/addon/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) {
2121
if (config.ignoreEmberOnErrorWarning) {
2222
return;
2323
}
24-
next(null, function () {
24+
next(null, function() {
2525
warn(
2626
'Ember.onerror found. Using Ember.onerror can hide some errors (such as flushed runloop errors) from Sentry. Use Sentry.captureException to capture errors within Ember.onError or remove it to have errors caught by Sentry directly. This error can be silenced via addon configuration.',
2727
!Ember.onerror,
@@ -33,9 +33,11 @@ export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) {
3333
});
3434
}
3535

36+
export function addTracing() {}
37+
3638
function createEmberEventProcessor(): void {
3739
if (addGlobalEventProcessor) {
38-
addGlobalEventProcessor((event) => {
40+
addGlobalEventProcessor(event => {
3941
event.sdk = {
4042
...event.sdk,
4143
name: 'sentry.javascript.ember',
@@ -54,4 +56,5 @@ function createEmberEventProcessor(): void {
5456
}
5557
}
5658

59+
export default Sentry;
5760
export * from '@sentry/browser';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import ApplicationInstance from '@ember/application/instance';
2+
import environmentConfig from 'ember-get-config';
3+
import Sentry from '@sentry/ember';
4+
5+
export function initialize(appInstance: ApplicationInstance): void {
6+
const config = environmentConfig['@sentry/ember'];
7+
if (config['disablePerformance']) {
8+
return;
9+
}
10+
instrumentForPerformance(appInstance);
11+
}
12+
13+
function getTransitionInformation(transition: any, router: any) {
14+
const fromRoute = transition?.from?.name;
15+
const toRoute = transition ? transition.to.name : router.currentRouteName;
16+
return {
17+
fromRoute,
18+
toRoute,
19+
};
20+
}
21+
22+
export async function instrumentForPerformance(appInstance: ApplicationInstance) {
23+
const config = environmentConfig['@sentry/ember'];
24+
const sentryConfig = config.sentry;
25+
const tracing = await import('@sentry/tracing');
26+
27+
const router = appInstance.lookup('service:router');
28+
29+
sentryConfig['integrations'] = [
30+
new tracing.Integrations.BrowserTracing({
31+
routingInstrumentation: (startTransaction, startTransactionOnPageLoad) => {
32+
let activeTransaction: any;
33+
if (startTransactionOnPageLoad && window.location) {
34+
const routeInfo = router.recognize(window.location.pathname);
35+
activeTransaction = startTransaction({
36+
name: `route:${routeInfo.name}`,
37+
op: 'pageload',
38+
tags: {
39+
url: window.location.pathname,
40+
toRoute: routeInfo.name,
41+
'routing.instrumentation': '@sentry/ember',
42+
},
43+
});
44+
}
45+
46+
router.on('routeWillChange', (transition: any) => {
47+
const { fromRoute, toRoute } = getTransitionInformation(transition, router);
48+
activeTransaction = startTransaction({
49+
name: `route:${toRoute}`,
50+
op: 'navigation',
51+
tags: {
52+
fromRoute,
53+
toRoute,
54+
'routing.instrumentation': '@sentry/ember',
55+
},
56+
});
57+
});
58+
router.on('routeDidChange', () => {
59+
if (activeTransaction) {
60+
activeTransaction.finish();
61+
}
62+
});
63+
},
64+
idleTimeout: 2000,
65+
}),
66+
];
67+
68+
const browserClient = new Sentry.BrowserClient(sentryConfig);
69+
Sentry.getCurrentHub().bindClient(browserClient);
70+
}
71+
72+
export default {
73+
initialize,
74+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default, initialize } from '@sentry/ember/instance-initializers/sentry-performance';

packages/ember/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use strict';
22

33
module.exports = {
4-
name: require('./package').name
4+
name: require('./package').name,
5+
options: {
6+
babel: {
7+
plugins: [ require.resolve('ember-auto-import/babel-plugin') ]
8+
}
9+
}
510
};

packages/ember/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
},
2828
"dependencies": {
2929
"@sentry/browser": "5.20.1",
30+
"@sentry/tracing": "5.20.1",
3031
"@sentry/types": "5.20.1",
3132
"@sentry/utils": "5.20.1",
3233
"ember-get-config": "^0.2.4",

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ class TestFetchTransport extends Transports.FetchTransport {
2020
}
2121
}
2222

23-
InitSentryForEmber({ transport: TestFetchTransport });
23+
InitSentryForEmber({
24+
transport: TestFetchTransport,
25+
});
2426

2527
export default class App extends Application {
2628
modulePrefix = config.modulePrefix;
Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,3 @@
11
import Controller from '@ember/controller';
2-
import { tracked } from '@glimmer/tracking';
3-
import { action } from '@ember/object';
4-
import EmberError from '@ember/error';
5-
import { scheduleOnce } from '@ember/runloop';
6-
import RSVP from 'rsvp';
72

8-
export default class ApplicationController extends Controller {
9-
@tracked showComponents;
10-
11-
@action
12-
createError() {
13-
this.nonExistentFunction();
14-
}
15-
16-
@action
17-
createEmberError() {
18-
throw new EmberError('Whoops, looks like you have an EmberError');
19-
}
20-
21-
@action
22-
createCaughtEmberError() {
23-
try {
24-
throw new EmberError('Looks like you have a caught EmberError');
25-
} catch(e) {
26-
console.log(e);
27-
}
28-
}
29-
30-
@action
31-
createFetchError() {
32-
fetch('http://doesntexist.example');
33-
}
34-
35-
@action
36-
createAfterRenderError() {
37-
function throwAfterRender() {
38-
throw new Error('After Render Error');
39-
}
40-
scheduleOnce('afterRender', throwAfterRender);
41-
}
42-
43-
@action
44-
createRSVPRejection() {
45-
const promise = new RSVP.Promise((resolve, reject) => {
46-
reject('Promise rejected');
47-
});
48-
return promise;
49-
}
50-
51-
@action
52-
createRSVPError() {
53-
const promise = new RSVP.Promise(() => {
54-
throw new Error('Error within RSVP Promise');
55-
});
56-
return promise;
57-
}
58-
59-
@action
60-
toggleShowComponents() {
61-
this.showComponents = !this.showComponents;
62-
}
63-
}
3+
export default class ApplicationController extends Controller {}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Controller from '@ember/controller';
2+
import { tracked } from '@glimmer/tracking';
3+
import { action } from '@ember/object';
4+
import EmberError from '@ember/error';
5+
import { scheduleOnce } from '@ember/runloop';
6+
import RSVP from 'rsvp';
7+
8+
export default class IndexController extends Controller {
9+
@tracked showComponents;
10+
11+
@action
12+
createError() {
13+
this.nonExistentFunction();
14+
}
15+
16+
@action
17+
createEmberError() {
18+
throw new EmberError('Whoops, looks like you have an EmberError');
19+
}
20+
21+
@action
22+
createCaughtEmberError() {
23+
try {
24+
throw new EmberError('Looks like you have a caught EmberError');
25+
} catch (e) {
26+
console.log(e);
27+
}
28+
}
29+
30+
@action
31+
createFetchError() {
32+
fetch('http://doesntexist.example');
33+
}
34+
35+
@action
36+
createAfterRenderError() {
37+
function throwAfterRender() {
38+
throw new Error('After Render Error');
39+
}
40+
scheduleOnce('afterRender', throwAfterRender);
41+
}
42+
43+
@action
44+
createRSVPRejection() {
45+
const promise = new RSVP.Promise((resolve, reject) => {
46+
reject('Promise rejected');
47+
});
48+
return promise;
49+
}
50+
51+
@action
52+
createRSVPError() {
53+
const promise = new RSVP.Promise(() => {
54+
throw new Error('Error within RSVP Promise');
55+
});
56+
return promise;
57+
}
58+
59+
@action
60+
toggleShowComponents() {
61+
this.showComponents = !this.showComponents;
62+
}
63+
}

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

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

99
Router.map(function() {
10+
this.route('tracing');
1011
});

packages/ember/tests/dummy/app/styles/app.css

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ body {
1717
background-repeat: repeat;
1818
height: 100%;
1919
margin: 0;
20-
font-family: Rubik,Avenir Next,Helvetica Neue,sans-serif;
20+
font-family: Rubik, Avenir Next, Helvetica Neue, sans-serif;
2121
font-size: 16px;
2222
line-height: 24px;
2323
color: var(--foreground-color);
@@ -43,7 +43,7 @@ body {
4343
.box {
4444
background-color: #fff;
4545
border: 0;
46-
box-shadow: 0 0 0 1px rgba(0,0,0,.08),0 1px 4px rgba(0,0,0,.1);
46+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.1);
4747
border-radius: 4px;
4848
display: flex;
4949
width: 100%;
@@ -54,8 +54,8 @@ body {
5454
padding-top: 20px;
5555
width: 60px;
5656
background: #564f64;
57-
background-image: linear-gradient(-180deg,rgba(52,44,62,0),rgba(52,44,62,.5));
58-
box-shadow: 0 2px 0 0 rgba(0,0,0,.1);
57+
background-image: linear-gradient(-180deg, rgba(52, 44, 62, 0), rgba(52, 44, 62, 0.5));
58+
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1);
5959
border-radius: 4px 0 0 4px;
6060
margin-top: -1px;
6161
margin-bottom: -1px;
@@ -73,12 +73,37 @@ body {
7373
background-image: url('/assets/images/sentry-logo.svg');
7474
}
7575

76+
.nav {
77+
display: flex;
78+
justify-content: center;
79+
padding: 10px;
80+
padding-top: 20px;
81+
padding-bottom: 0px;
82+
}
83+
84+
.nav a {
85+
padding-left: 10px;
86+
padding-right: 10px;
87+
font-weight: 500;
88+
text-decoration: none;
89+
color: var(--foreground-color);
90+
}
91+
92+
.nav a.active {
93+
border-bottom: 4px solid #6c5fc7;
94+
}
95+
7696
section.content {
7797
flex: 1;
7898
padding-bottom: 40px;
7999
}
80100

81-
h1, h2, h3, h4, h5, h6 {
101+
h1,
102+
h2,
103+
h3,
104+
h4,
105+
h5,
106+
h6 {
82107
font-weight: 600;
83108
}
84109

@@ -101,7 +126,8 @@ div.section {
101126
padding-top: 20px;
102127
}
103128

104-
.content-container h3, .content-container h4 {
129+
.content-container h3,
130+
.content-container h4 {
105131
margin-top: 0px;
106132
}
107133

@@ -113,7 +139,7 @@ button {
113139
border-radius: 3px;
114140
font-weight: 600;
115141
padding: 8px 16px;
116-
transition: all .1s;
142+
transition: all 0.1s;
117143

118144
border: 1px solid transparent;
119145
border-radius: 3px;
@@ -139,8 +165,8 @@ button.primary {
139165

140166
display: inline-block;
141167

142-
text-shadow: 0 -1px 0 rgba(0,0,0,.15);
143-
box-shadow: 0 2px 0 rgba(0,0,0,.08);
168+
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15);
169+
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.08);
144170

145171
text-transform: none;
146172
overflow: visible;
@@ -154,7 +180,6 @@ button.primary:hover {
154180
button.primary:focus {
155181
background: #5b4cc0;
156182
border-color: #3a2f87;
157-
box-shadow: inset 0 2px 0 rgba(0,0,0,.12);
183+
box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.12);
158184
outline: none;
159185
}
160-

0 commit comments

Comments
 (0)