-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12398 from guardian/ds/add-tags-to-injectSentry
Update reportError Function to support Custom Tags in window Object
- Loading branch information
Showing
5 changed files
with
131 additions
and
81 deletions.
There are no files selected for viewing
183 changes: 111 additions & 72 deletions
183
dotcom-rendering/src/client/sentryLoader/loadSentry.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,132 @@ | ||
import { isAdBlockInUse } from '@guardian/commercial'; | ||
import { log, startPerformanceMeasure } from '@guardian/libs'; | ||
import '../webpackPublicPath'; | ||
import type { ReportError } from '../../types/sentry'; | ||
|
||
/** Sentry errors are only sent to the console */ | ||
const stubSentry = (): void => { | ||
window.guardian.modules.sentry.reportError = (error) => { | ||
console.error(error); | ||
}; | ||
}; | ||
type ReportErrorError = Parameters<ReportError>[0]; | ||
type ReportErrorFeature = Parameters<ReportError>[1]; | ||
type ReportErrorTags = Parameters<ReportError>[2]; | ||
|
||
/** | ||
* Set up error handlers to inject and call Sentry. | ||
* If no error happen, Sentry is not loaded. | ||
*/ | ||
const loadSentryOnError = (): void => { | ||
try { | ||
// Downloading and initialising Sentry is asynchronous so we need a way | ||
// to ensure injection only happens once and to capture any other errors that | ||
// might happen while this script is loading | ||
let injected = false; | ||
const queue: Error[] = []; | ||
type ErrorQueue = Array<{ | ||
error: ReportErrorError; | ||
feature: ReportErrorFeature; | ||
tags?: ReportErrorTags; | ||
}>; | ||
|
||
// Function that gets called when an error happens before Sentry is ready | ||
const injectSentry = async ( | ||
error: Error | undefined, | ||
feature: string, | ||
) => { | ||
const { endPerformanceMeasure } = startPerformanceMeasure( | ||
'dotcom', | ||
'sentryLoader', | ||
'inject', | ||
); | ||
// Remember this error for later | ||
if (error) queue.push(error); | ||
const loadSentryCreator = () => { | ||
/** | ||
* Downloading and initialising Sentry is asynchronous so we need a way | ||
* to ensure initialisation only happens once and to queue and report any | ||
* error that might happen while this script is loading | ||
*/ | ||
let initialised = false; | ||
const errorQueue: ErrorQueue = []; | ||
|
||
// Only inject once | ||
if (injected) { | ||
return; | ||
} | ||
injected = true; | ||
/** | ||
* Function that gets called when an error happens before Sentry is ready | ||
*/ | ||
const loadSentry = async ( | ||
error: ReportErrorError | undefined, | ||
feature: ReportErrorFeature = 'unknown', | ||
tags?: ReportErrorTags, | ||
) => { | ||
const { endPerformanceMeasure } = startPerformanceMeasure( | ||
'dotcom', | ||
'sentryLoader', | ||
'initialise', | ||
); | ||
/** | ||
* Queue this error for later | ||
*/ | ||
if (error) errorQueue.push({ error, feature, tags }); | ||
|
||
// Make this call blocking. We are queing errors while we wait for this code to run | ||
// so we won't miss any and by waiting here we ensure we will never make calls we | ||
// expect to be blocked | ||
// Ad blocker detection can be expensive so it is checked here rather than in init | ||
// to avoid blocking of the init flow | ||
const adBlockInUse: boolean = await isAdBlockInUse(); | ||
if (adBlockInUse) { | ||
// Ad Blockers prevent calls to Sentry from working so don't try to load the lib | ||
return; | ||
} | ||
/** | ||
* Only initialise once | ||
*/ | ||
if (initialised) { | ||
return; | ||
} | ||
initialised = true; | ||
|
||
// Load sentry.ts | ||
const { reportError } = await import( | ||
/* webpackChunkName: "lazy" */ | ||
/* webpackChunkName: "sentry" */ './sentry' | ||
); | ||
/** | ||
* Make this call blocking. We are queing errors while we wait for this code to run | ||
* so we won't miss any and by waiting here we ensure we will never make calls we | ||
* expect to be blocked | ||
* Ad blocker detection can be expensive so it is checked here rather than in init | ||
* to avoid blocking of the init flow | ||
*/ | ||
const adBlockInUse: boolean = await isAdBlockInUse(); | ||
if (adBlockInUse) { | ||
/** | ||
* Ad Blockers prevent calls to Sentry from working so don't try to load the lib | ||
*/ | ||
return; | ||
} | ||
|
||
// Sentry takes over control of the window.onerror and | ||
// window.onunhandledrejection listeners but we need to | ||
// manually redefine our own custom error reporting function | ||
window.guardian.modules.sentry.reportError = reportError; | ||
/** | ||
* Dynamically load sentry.ts | ||
*/ | ||
const { reportError } = await import( | ||
/* webpackChunkName: "lazy" */ | ||
/* webpackChunkName: "sentry" */ './sentry' | ||
); | ||
|
||
// Now that we have the real reportError function available, | ||
// send any queued errors | ||
while (queue.length) { | ||
const queuedError = queue.shift(); | ||
if (queuedError) reportError(queuedError, feature); | ||
/** | ||
* Replace the lazy loader stub with our custom error reporting function | ||
*/ | ||
window.guardian.modules.sentry.reportError = reportError; | ||
|
||
/** | ||
* Now that we have the real reportError function available, | ||
* report any queued errors | ||
*/ | ||
while (errorQueue.length) { | ||
const queuedError = errorQueue.shift(); | ||
if (queuedError) { | ||
reportError( | ||
queuedError.error, | ||
queuedError.feature, | ||
queuedError.tags, | ||
); | ||
} | ||
log('dotcom', `Injected Sentry in ${endPerformanceMeasure()}ms`); | ||
}; | ||
} | ||
log('dotcom', `Initialise Sentry in ${endPerformanceMeasure()}ms`); | ||
}; | ||
return loadSentry; | ||
}; | ||
|
||
/** | ||
* Set up error handlers to initialise and call Sentry when an error occurs. | ||
* If no error happens, Sentry is not loaded. | ||
*/ | ||
const loadSentryOnError = (): void => { | ||
try { | ||
const loadSentry = loadSentryCreator(); | ||
|
||
// This is how we lazy load Sentry. We setup custom functions and | ||
// listeners to inject Sentry when an error happens | ||
/** | ||
* This is how we lazy load Sentry. We setup custom functions and | ||
* listeners to initialise Sentry when an error happens | ||
* | ||
* Sentry will replace onerror and onunhandledrejection listeners | ||
* with its own handlers once initialised | ||
* | ||
* reportError is replaced by loadSentry | ||
*/ | ||
window.onerror = (message, url, line, column, error) => | ||
injectSentry(error, 'unknown'); | ||
loadSentry(error); | ||
window.onunhandledrejection = ( | ||
event: undefined | { reason?: unknown }, | ||
) => | ||
event?.reason instanceof Error && | ||
injectSentry(event.reason, 'unknown'); | ||
window.guardian.modules.sentry.reportError = (error, feature) => { | ||
injectSentry(error, feature).catch((e) => | ||
console.error(`injectSentry - error: ${String(e)}`), | ||
) => event?.reason instanceof Error && loadSentry(event.reason); | ||
window.guardian.modules.sentry.reportError = (error, feature, tags) => { | ||
loadSentry(error, feature, tags).catch((e) => | ||
// eslint-disable-next-line no-console -- fallback to console.error | ||
console.error(`loadSentryOnError error: ${String(e)}`), | ||
); | ||
}; | ||
} catch { | ||
// We failed to setup Sentry :( | ||
} catch (e) { | ||
// eslint-disable-next-line no-console -- fallback to console.error | ||
console.error(`loadSentryOnError error: ${String(e)}`); | ||
} | ||
}; | ||
|
||
export { loadSentryOnError, stubSentry }; | ||
export { loadSentryOnError }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export type ReportError = ( | ||
error: Error, | ||
feature: string, | ||
tags?: { | ||
[key: string]: string; | ||
}, | ||
) => void; |