Monitoring javascript errors using window.onerror

The material, the translation of which we are publishing today, is dedicated to handling JS errors using window.onerror . This is a special browser event that is triggered when uncaught errors occur. Here we will talk about how to intercept errors using an onerror event onerror , and how to send information about them to the website developer server. This handler can be used as the basis of your own system for collecting and analyzing information about errors. In addition, it is one of the most important mechanisms used in error-oriented libraries, such as raven-js .

image

Listening for the window.onerror event


You can listen for the onerror event by assigning a window.onerror function that plays the role of an error handler:

 window.onerror = function(msg, url, lineNo, columnNo, error) { // ...   ... return false; } 

This function is called when an error occurs, and the following arguments are passed to it:


The first four arguments tell the developer which script, which row, and which column of the row the error occurred. The last argument, an object of type Error , is perhaps the most important of all the arguments. Let's talk about it.

Error object and Error.prototype.stack property


At first glance, there is nothing special about the Error object. It contains three completely standard properties - message , fileName and lineNumber . This data, given the information passed to the window.onerror event handler, can be considered redundant.

The real value in this case is a non-standard property Error.prototype.stack . This property gives access to the call stack (error stack), allows you to know what was happening in the program at the time of the error, what functions call preceded its appearance. Tracing the call stack can be an important part of the debugging process. And, despite the fact that the stack property is not standard, it is available in all modern browsers.

Here is the stack property of the error object in Chrome 46.

 "Error: foobar\n    at new bar (<anonymous>:241:11)\n    at foo (<anonymous>:245:5)\n at <anonymous>:250:5\n    at <anonymous>:251:3\n at <anonymous>:267:4\n at callFunction (<anonymous>:229:33)\n    at <anonymous>:239:23\n at <anonymous>:240:3\n at Object.InjectedScript.\_evaluateOn (<anonymous>:875:140)\n    at Object.InjectedScript.\_evaluateAndWrap (<anonymous>:808:34)" 

Before us is an unformatted string. When the contents of this property are presented in this form, it is inconvenient to work with it. Here's how the same will look after formatting.

 Error: foobar at new bar (<anonymous>:241:11) at foo (<anonymous>:245:5) at callFunction (<anonymous>:229:33) at Object.InjectedScript._evaluateOn (<anonymous>:875:140) at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34) 

Now, after formatting, the error stack looks much clearer, it immediately becomes clear why the stack property is very important when debugging errors.

However, everything here is not smooth. The stack property is not standardized, it is implemented differently in different browsers. Here, for example, looks like an error stack in Internet Explorer 11.

 Error: foobar at bar (Unknown script code:2:5) at foo (Unknown script code:6:5) at Anonymous function (Unknown script code:11:5) at Anonymous function (Unknown script code:10:2) at Anonymous function (Unknown script code:1:73) 

It can be noted, in comparison with the previous example, that not only a different presentation format of stack frames is used here, but also that less data is given for each frame. For example, Chrome identifies instances of using the new keyword and provides more detailed information about other events (in particular, function calls. _evaluateOn and. _evaluateAndWrap ). In this case, we compared only what is issued by IE and Chrome. Other browsers have used their own approaches to displaying data about the stack and to the selection of information included in this data.

In order to bring all this to a uniform form, you can use third-party tools. For example, in raven-js TraceKit is used for this. Stacktrace.js and some other projects serve the same purpose.

Features support window.onerror different browsers


The windows.onerror event has existed in browsers for quite some time. In particular, it can be found in IE6 and in Firefox 2. The problem here is that all browsers implement windows.onerror differently. For example, this concerns the number and structure of the arguments passed to the event handlers of this event.

Here is a table that contains information about the arguments passed to the onerror handler in the main browsers.
Browser
message
url
lineNo
colNo
errorObj
Firefox
there is
there is
there is
there is
there is
Chrome
there is
there is
there is
there is
there is
Edge
there is
there is
there is
there is
there is
IE 11
there is
there is
there is
there is
there is
IE10
there is
there is
there is
there is
Not
IE 9.8
there is
there is
there is
Not
Not
Safari 10 and up
there is
there is
there is
there is
there is
Safari 9
there is
there is
there is
there is
Not
Android Browser 4.4
there is
there is
there is
there is
Not

It is probably not surprising that Internet Explorer 8, 9, and 10 have limited onerror support. However, it may seem unusual that in Safari browser support for the error object appeared only in the 10th version, released in 2016. In addition, there are outdated mobile devices that use the standard Android browser, which also does not support the error object. In modern versions of Android, this browser is replaced by Chrome Mobile.

If we have no error object at our disposal, then there is no stack trace data either. This means that browsers that do not support the error object do not provide, in the standard onerror handler onerror , stack information. And this, as we have said, is very important.

Designing a polyfill for window.onerror using a try / catch construct


In order to get information about the stack in browsers that do not support passing an onerror object to the onerror handler, you can use the following trick. You can wrap the code in a try/catch construct and catch the errors yourself. The resulting error object will contain, in all modern browsers, what we need - the stack property.
Take a look at the code of the invoke() helper method, which calls the specified object method, passing in an array of arguments.

 function invoke(obj, method, args) { return obj[method].apply(this,args); } 

Here's how to use it.

 invoke(Math, 'max', [1,2]); //  2 

Here is the same invoke() , but now the method call is wrapped in try/catch , which allows you to intercept possible errors.

 function invoke(obj, method, args) { try {   return obj[method].apply(this,args); } catch(e) {   captureError(e);//      throw e;//      } } invoke(Math,'highest',[1,2]); //  ,     Math.highest 

Of course, it is very expensive to manually add such structures to all places where they may be needed. This task can be simplified by creating a universal auxiliary function.

 function wrapErrors(fn) { //      if(!fn.__wrapped__) {   fn.__wrapped__ = function() {     try{       return fn.apply(this,arguments);     }catch(e){       captureError(e);//          throw e;//          }   }; } return fn.__wrapped__; } var invoke = wrapErrors(function(obj, method, args) { returnobj[method].apply(this,args); }); invoke(Math,'highest',[1,2]);//,   Math.highest 

Since JavaScript uses a single-threaded code execution model, this wrapper should be used only with those function calls that are at the beginning of new stacks. There is no need to wrap absolutely all function calls into it.

As a result, it turns out that this function should be used in the following places:


Here is an example of using the wrapErrors function.

 $(wrapErrors(function () {//    doSynchronousStuff1();//     setTimeout(wrapErrors(function () {   doSynchronousStuff2();//      })); $('.foo').click(wrapErrors(function () {   doSynchronousStuff3();//     })); })); 

Such constructions can be added to the code yourself, but this is too time consuming task. As a convenient alternative in such situations, you can consider libraries to work with errors, which, for example, have mechanisms that addEventListener and setTimeout tools to intercept errors.

Transfer of errors to the server


So, now we have at our disposal means for intercepting error information either using windows.onerror or using auxiliary functions based on try/catch . These errors occur on the client side, and, after intercepting them, we would like to deal with them and take measures to eliminate them. For this they need to be transferred to our server. In order to do this, you need to prepare a web service that would accept information about errors via HTTP, after which it would somehow save them for further processing, say, write to a log file or database.

If this web service is located on the same domain as the web application, XMLHttpRequest will be sufficient. The following example shows how to use a function to execute AJAX requests from jQuery to transfer data to a server.

 function captureError(ex){ var errorData = {   name:ex.name,// : ReferenceError   message:ex.line,// : x is undefined   url:document.location.href,   stack:ex.stack//   ; ,     ! }; $.post('/logger/js/',{   data:errorData }); } 

Keep in mind that if you need to send cross-domain requests to send information about errors to the server, you will have to take care of the support of such requests.

Results


You have learned the basics of creating a service to intercept errors and send information about them to the server. In particular, here we considered the following questions:


After learning how the mechanisms described above work, you acquired basic knowledge that will allow you to start creating your own system for working with errors, specifying additional details during the work. Perhaps this scenario is especially relevant for those cases when it comes to some application in which, say, for security reasons, there are no plans to use third-party libraries. If your application allows the use of third-party code, you may well find the right tool for monitoring JS errors. Among such tools are Sentry , Rollbar , TrackJS and other similar projects.

Dear readers! What tools for monitoring JS errors do you use?

Source: https://habr.com/ru/post/413173/


All Articles