-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathregisterSentry.ts
296 lines (253 loc) · 10.3 KB
/
registerSentry.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import {
addEventProcessor,
browserTracingIntegration as originalBrowserTracingIntegration,
captureException,
getActiveSpan,
getCurrentScope,
getRootSpan,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
setHttpStatus,
startBrowserTracingNavigationSpan,
} from '@sentry/browser'
import type { Integration, Span, SpanAttributes, TransactionSource } from '@sentry/core'
import {
browserProfilingIntegration,
browserSessionIntegration,
extraErrorDataIntegration,
httpClientIntegration,
httpContextIntegration,
init as sentryInit,
reportingObserverIntegration,
thirdPartyErrorFilterIntegration,
vueIntegration,
} from '@sentry/vue'
import type { App } from 'vue'
import type { Router } from 'vue-router'
import { useSettingsStore } from '@/stores/settings'
export default function registerSentry(app: App, router: Router) {
if (!import.meta.env.VITE_SENTRY_ENABLED) return
const { dataCollectionCrashes, dataCollectionPerformance } = useSettingsStore()
if (!dataCollectionCrashes && !dataCollectionPerformance) return
// Get release prefixes and suffixes from config
const releasePrefix = import.meta.env.VITE_SENTRY_RELEASE_PREFIX || ''
const releaseSuffix = import.meta.env.VITE_SENTRY_RELEASE_SUFFIX || ''
// Get traces and profiles sample rates from config
const tracesSampleRate = import.meta.env.VITE_SENTRY_TRACES_SAMPLE_RATE
const profilesSampleRate = import.meta.env.VITE_SENTRY_PROFILES_SAMPLE_RATE
// Track only base components for performance
const trackedComponents = [
'VApp',
'VAppBar',
'VMain',
'RouterView',
'NavigationDesktop',
'NavigationMobile',
'NavigationDay',
'ViewTimetable',
'ViewMenu',
'ViewCirculars',
'ViewSources',
'ViewSubscribe',
'ViewSettings',
'ViewWelcome',
'NotFound',
]
// Include additional always-enabled integrations
const integrations = [
extraErrorDataIntegration({ depth: 8 }),
reportingObserverIntegration(),
httpClientIntegration(),
httpContextIntegration(),
thirdPartyErrorFilterIntegration({
filterKeys: [import.meta.env.VITE_SENTRY_APPLICATION_KEY],
behaviour: 'apply-tag-if-contains-third-party-frames',
}),
vueIntegration({
tracingOptions: {
trackComponents: dataCollectionPerformance ? trackedComponents : false,
},
}),
]
// Add performance integrations if enabled in settings
if (dataCollectionPerformance) {
integrations.push(browserSessionIntegration())
if (tracesSampleRate) integrations.push(browserTracingIntegration(router))
if (profilesSampleRate) integrations.push(browserProfilingIntegration())
}
// Init the Sentry SDK
sentryInit({
app,
dsn: import.meta.env.VITE_SENTRY_DSN,
tracesSampleRate: tracesSampleRate,
profilesSampleRate: profilesSampleRate,
maxBreadcrumbs: import.meta.env.VITE_SENTRY_MAX_BREADCRUMBS,
tracePropagationTargets: import.meta.env.VITE_SENTRY_TRACE_PROPAGATION_TARGETS,
normalizeDepth: 8,
environment: import.meta.env.MODE,
release: releasePrefix + import.meta.env.VITE_VERSION + releaseSuffix,
integrations,
})
// Add event processor to collect a few more useful metrics
addEventProcessor(function (event) {
// Make sure the context and tags objects exists
if (!event.contexts) event.contexts = {}
if (!event.tags) event.tags = {}
// Add context from user settings that can be useful when debugging errors
const settingsStore = useSettingsStore()
event.contexts['Settings - General'] = {
'Show Substitutions': settingsStore.showSubstitutions,
'Show Links in Timetable': settingsStore.showLinksInTimetable,
'Show Hours in Timetable': settingsStore.showHoursInTimetable,
'Highlight Current Time': settingsStore.highlightCurrentTime,
'Enable Lesson Details': settingsStore.enableLessonDetails,
'Enable Pull To Refresh': settingsStore.enablePullToRefresh,
'Theme Type': settingsStore.themeType,
'Accent Color': settingsStore.accentColor,
}
event.contexts['Settings - Entity'] = {
'Entity Type': settingsStore.entityType,
'Entity List': settingsStore.entityList,
}
event.contexts['Settings - Food'] = {
'Snack Type': settingsStore.snackType,
'Lunch Type': settingsStore.lunchType,
}
// Add tags based on user settings
event.tags['settings.show_substitutions'] = settingsStore.showSubstitutions
event.tags['settings.show_links_in_timetable'] = settingsStore.showLinksInTimetable
event.tags['settings.show_hours_in_timetable'] = settingsStore.showHoursInTimetable
event.tags['settings.highlight_current_time'] = settingsStore.highlightCurrentTime
event.tags['settings.enable_lesson_details'] = settingsStore.enableLessonDetails
event.tags['settings.enable_pull_to_refresh'] = settingsStore.enablePullToRefresh
event.tags['settings.accent_color'] = settingsStore.accentColor
event.tags['settings.has_moodle_token'] = !!settingsStore.moodleToken
event.tags['settings.has_circulars_password'] = !!settingsStore.circularsPassword
event.tags['settings.type.theme'] = settingsStore.themeType
event.tags['settings.type.entity'] = settingsStore.entityType
event.tags['settings.type.snack'] = settingsStore.snackType
event.tags['settings.type.lunch'] = settingsStore.lunchType
// Add tags based on a few relevant media queries
const displayModes = [
'browser',
'standalone',
'fullscreen',
'minimal-ui',
'window-controls-overlay',
'picture-in-picture',
]
for (const displayMode of displayModes) {
if (window.matchMedia(`(display-mode: ${displayMode})`).matches) {
event.tags['media.display_mode'] = displayMode
break
}
}
const colorSchemes = ['light', 'dark']
for (const colorScheme of colorSchemes) {
if (window.matchMedia(`(prefers-color-scheme: ${colorScheme})`).matches) {
event.tags['media.color_scheme'] = colorScheme
break
}
}
// Return the modified event
return event
})
}
/**
* A custom browser tracing integration for Vue.
*
* Based on the original Sentry Vue instrumentation but modified to use our
* custom transaction name formats and include more context data about events.
*
* Sources:
* * https://github.com/getsentry/sentry-javascript/blob/develop/packages/vue/src/browserTracingIntegration.ts
* * https://github.com/getsentry/sentry-javascript/blob/develop/packages/vue/src/router.ts
*/
function browserTracingIntegration(router: Router): Integration {
const integration = originalBrowserTracingIntegration({ instrumentNavigation: false })
const instrumentNavigation = true
const instrumentPageLoad = true
return {
...integration,
afterAllSetup(client) {
integration.afterAllSetup(client)
router.onError(error => captureException(error, { mechanism: { handled: false } }))
router.beforeEach((to, from) => {
const attributes: SpanAttributes = {
'route.name': to.name as string,
'route.path': to.path as string,
'route.hash': to.hash as string,
}
for (const key of Object.keys(to.params)) {
attributes[`route.params.${key}`] = to.params[key]
}
for (const key of Object.keys(to.query)) {
const value = to.query[key]
if (value) attributes[`route.query.${key}`] = value
}
// Determine a name for the routing transaction
let transactionName: string = to.path
let transactionSource: TransactionSource = 'route'
let transactionHttpStatus: number | undefined = undefined
// Parametrize timetable transactions in the same style as backend
if (
to.name === 'timetable' &&
['classes', 'teachers', 'classrooms'].includes(to.params.type as string) &&
to.params.value
) {
if (!(to.params.type === 'classrooms' && to.params.value === 'empty')) {
transactionName = to.matched[0].path
.replace(':type?', to.params.type as string)
.replace(':value?', `<${to.params.type}>`)
}
} else if (to.name === 'welcome') {
transactionName = '/welcome'
transactionSource = 'custom'
} else if ((to.name === 'timetable' && to.params.type) || to.name === 'notFound') {
transactionName = 'generic 404 request'
transactionSource = 'custom'
transactionHttpStatus = 404
}
getCurrentScope().setTransactionName(transactionName)
const isPageLoadNavigation = from.name === undefined && from.matched.length === 0
const isNotFoundNavigation = from.path === to.path && to.name === 'notFound'
if (instrumentPageLoad && isPageLoadNavigation) {
const activeRootSpan = getActiveRootSpan()
if (activeRootSpan) {
// Replace the name of the existing root span
activeRootSpan.updateName(transactionName)
// Set router attributes on the existing pageload transaction
// This will override the source and origin and add params & query attributes
activeRootSpan.setAttributes({
...attributes,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: transactionSource,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue',
})
// Set the HTTP status if it was specified
if (transactionHttpStatus) {
setHttpStatus(activeRootSpan, transactionHttpStatus)
}
}
}
if (instrumentNavigation && !isPageLoadNavigation && !isNotFoundNavigation) {
attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = transactionSource
attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = 'auto.navigation.vue'
// Start a new navigation transaction
const navigationSpan = startBrowserTracingNavigationSpan(client, {
name: transactionName,
op: 'navigation',
attributes,
})
// Set the HTTP status if it was specified
if (navigationSpan && transactionHttpStatus) {
setHttpStatus(navigationSpan, transactionHttpStatus)
}
}
})
},
}
}
function getActiveRootSpan(): Span | undefined {
const span = getActiveSpan()
if (span) return getRootSpan(span)
}