Error tracking is one of the awful part of JavaScript. Server side error tracking requires several configuration, which is not hard because server is under developers’ full control, after all. For Android/iOS applications, error tracking is integrated into integrated into platform framework. Unfortunately, error tracking in browser is like survival in wild jungle.
Here are some common pitfalls.
Incompatible API
JavaScript errors in different browsers have different field names, as usual.
One should never bother to care about api incompatibility among browsers. Here is the snippet to normalize those quirks.
Curse the variable ieEvent
.
1 | function errorTracker (err) { |
CORS Header
You will see a lot of Scritp error
message in your CDN hosted javascript file. Browsers report this useless error intentially. Revealing error details to web page in different domain is a security issue. It can leak one’s privacy and helps phishing and social engineering. To quote the SO answer
This behavior is intentional, to prevent scripts from leaking information to external domains. For an example of why this is necessary, imagine accidentally visiting evilsite.com, that serves up a page with
<script src="yourbank.com/index.html">
. (yes, we’re pointing that script tag at html, not JS). This will result in a script error, but the error is interesting because it can tell us if you’re logged in or not. If you’re logged in, the error might be'Welcome Fred...'
is undefined, whereas if you’re not it might be'Please Login ...'
is undefined. Something along those lines.
And in Chromium’s source code, we can see error is sanitized, if the corsStatus
does not satisify some condition.
1 | bool ExecutionContext::shouldSanitizeScriptError(const String& sourceURL, AccessControlStatus corsStatus) |
To enable SharableCrossOrigin
scripts, one can add crossorigin
attribute to script tags, and, add a Access-Control-Allow-Origin
head in scripts’ server response, just like cross orgin XHR.
Like this.
1 | <script src="http://somremotesite.example/script.js" crossorigin></script> |
And in your server conf, say, nginx, add something like this
1 | server { |
More Browser Quirks and nasty ISP
Modern browser will protect users’ privacy and respect developers’ CORS setting. But IE may screw both. In some unpatched Internet Exploers, all script errors are accessible in onError
handler, regardless of their origins. But some Internet Explorers, patched, just ignore the CORS head and swallow all crossorigin error messages.
To catch errors in certain IEs, developers must manually wrap their code in try {...} catch (e){report(e)}
block. Alternatively, one can use build process to wrap function, like this.
Zone should also be a good candidate for error tracking, and does not require build process. Though I have not tried it.
Another issue in error tracking is ISP and browser extensions. onError
callbacks will receive all error in the host page. It usually contains many ISP injected script and extension script which trigger false alarm errors. So wrapping code in try ... catch
may be a better solution.
UPDATE:
It seems Zone
like hijacking method has been used in error tracking product. Like BugSnag. The basic idea is: If code is executed synchronously, then it can be try ... catch ...
ed in one single main function. If code is executed asynchronously, then, by wrapping all function that takes callback, one can wrap all callbacks in try ...catch ...
.
1 | function wrap(func) { |
The above code will wrap all func
in try and catch. So when error occurs, it will be always logged. However, calling wrapper function in every async code usage is impractical. We can invert it! Not wrapping callbacks, but wrapping functions that consume callbacks, say, setTimeout
, addEventListener
, etc. Once these async code entries have been wrapped, all callbacks are on track.
And, because JavaScript is prototype based language, we can hijack
the EventTarget
prototype and automate our error tracking code.
1 | var addEventListener = window.EventTarget.prototype.addEventListener; |
IE9 and friends
Sadly, IE does not give us stack on error. But we can hand-roll our call stack by traversing argument.callee.caller
.
1 | // IE <9 |
Garbage Collector Issue
Error reporting is usually done by inserting an Image
of which the url
is the address of logging server comprised of encoded error info in query string.
1 | var url = 'xxx'; |
But the Image
has no reference to itself, and JS engine’s garbage collector will collect it before the request is sent. So one can assign the Image
to a variable to hold its reference, and withdraw the reference in the onload/onerror
callback.
1 | var win = window; |