Make your 'Script error.' issues more insightful in Sentry
Posted on
Context and problem
For quite some time we had our Sentry monitoring capturing the following very unperceptive error:
Event
ErrorEvent
captured as exception with messageScript error.
Eventually I landed on this rather comprehensive Sentry answers article: What is "Script Error"?.
According to the article...
“Script error” is what browsers send to the
onerror
callback when an error originates from a JavaScript file served from a different origin (different domain, port, or protocol).
The article gives the recommendation of using crossorigin="anonymous"
on the
script that originated the error.
The problem is that the exception captured in Sentry has absolutely no reference to the culprit script, leaving us clueless on how to address the issue.
Vision of an improvement
The obvious improvement would be to be provided with the name of the culprit script.
At best, to be pointed at the line, in that script, that originated the unhandled exception.
A solution
Experimenting with the window.error
callback presented in the article, I was
able to log the url
argument that revealed the chased script.
Now, it was a matter of figuring out how to tell Sentry to let us send them a better formatted error message, that would include the clue about which script is the culprit, as well as some insight how to get even more details.
This is how I made it all work...
First, in the configuration passed to the Sentry.init()
function call, make
sure to include within the ignoreErrors
array the string: 'Script error.'
.
That basically tells Sentry to ignore this error all together.
Secondly, where appropriate in your use case, include a window.onerror
callback definition like the one below:
/**
* Customises specific error before capturing to Sentry. It aims at providing
* more insights than what the default Sentry capture would do for such errors.
*
* The parameters are the one provided by the `window.onerror` callback.
*
* @param {string} message Error message.
* @param {string} url URL of the script which threw the exception.
* @param {object} [error] Error being thrown.
*/
function addSentryOverrides(message, url, error) {
// Warning: For this to work, 'Script error.' should be added to Sentry.init
// `ignoreErrors` list.
if (error === undefined && message && message.includes('Script error.')) {
const formattedMessage = `Unhandled exception in script ${url}. Hint: Add \`crossorigin="anonymous"\` on the script tag to know the type of error and the position of the error`;
const scriptError = new Error(formattedMessage);
scriptError.name = 'ScriptError';
Sentry.captureException(scriptError);
}
}
window.onerror = function (message, url, _line, _column, error) {
addSentryOverrides(message, url, error);
};
Testing out the solution
In order to test out this solution, I created a temporary script over at Static Save that looks like this:
function foo() {
bar();
}
When calling foo()
this should blow up as bar()
is not defined.
Now let's use this...
After loading the page I use for the test; in the developer console, I went ahead and appended the temporary script programmatically as below:
var culpritRemoteScript = document.createElement('script');
culpritRemoteScript.setAttribute('src','https://temp.staticsave.com/68525ff025b09.js');
document.body.appendChild(culpritRemoteScript);
I then executed the below snippet in order to trigger the exception upon a user click interaction.
// Clicking the Home link will generate an Unhandled exception
document
.querySelector('[data-test="home-link"]')
.addEventListener('click', function clickEventHandler(event) {
event.preventDefault();
foo();
});
Now, as soon as we click the "Home" anchor link, the foo()
function will be
called, what in turns will be caught into the window.error
callback defined
previously, what would finally capture the following exception in Sentry:
ScriptError
Unhandled exception in script https://temp.staticsave.com/68525ff025b09.js. Hint: Addcrossorigin="anonymous"
on the script tag to know the type of error and the position of the error
This is much more insightful, isn't it?
Sentry benefit from following the provided hint
Taken that we follow the "Hint" of adding the crossorigin
attribute to the
remote script, Sentry seems to capture the exception beautifully, in the sense
that, it turns into the following error message and stack trace
Message:
ReferenceError
bar is not defined
Strack trace:
ReferenceError: baz is not defined
at bar(/68525ff025b09.js:2:3)
As you can see it now reveals quite some insights: the error type, the script filename, and the position of the statement that threw the exception.
We are therefore better equipped to go and fix that bug.
Sharing is caring
I spent quite some time in researching how to address this inconvenience, as well as, crafting this solution.
I therefore hope that, if you find yourself in this situation, your path will cross this blog post and that it will fit to your need and make your day brighter 😉.
If you find any problem with the proposed solution and/or have a better approach, do not hesitate to reach out. So far this is the best one I think of.