简体   繁体   中英

Chrome automatically formats Error stacks, how does this work?

The content of (new Error('foo')).stack looks something like this:

Error: foo
    at Object.notifier (http://localhost:6969/js/main.js:12705:37)
    at trackHookChanges (http://localhost:6969/js/main.js:1813:27)
    at UseState (http://localhost:6969/js/main.js:1982:13)
    at K._.data (http://localhost:6969/js/main.js:70174:6005)
    at K (http://localhost:6969/js/main.js:70174:6380)
    at Z (http://localhost:6969/js/main.js:70174:9187)

However, when I console.log it, it looks like:

Error: foo
    at Object.notifier (wdyr.ts:10)
    at trackHookChanges (whyDidYouRender.js:1306)
    at UseState (whyDidYouRender.js:1475)
    at K._.data (index.esm.js:1)
    at K (index.esm.js:1)
    at Z (index.esm.js:1)

Is Chrome devtools is using sourcemaps to automatically change the string being logged? Is it there an easy way to access the source file names in my code? I want to ignore errors originating from certain NPM module.

Unfortunately (but luckly for devs) yes, Chrome uses sourcemaps to format errors in the console and there is (still) no way to access the same function it uses or output it produces. Even if it was possible, it would work only on a specific browser/platform.

TLDR

Emulate the browser sourcemap resolution with StackTraceJS or filter your errors by their prototype or any of their properties (like Error.message for example)

Discussion

JS Errors stacktrace are a mess, and so unreliable:

  • they are very dependant on the running environment, if you run code on Chrome you could end up with a different stack with respect of running it on Firefox, IE or node (even if in the latest times they are reaching to a "stacktrace agreement" between environments).
  • The maximum length of the error stacktrace is (almost always) 10 lines, so if your function (hook) was called before that time you will never catch it.
  • internal or delayed callbacks can erase/change/augment the stacktrace of a function in certain environments (it can be very hard, sometimes impossible, to catch the full stacktrace of a callback called inside a setTimeout for example)

Partial solution (StackTraceJS)

If you can afford to make a http request for a sourcemap, you can exploit the same mechanism that Chrome (or any other browser) uses to parse and map the error stacktrace files into the original files and filter those you don't like. The downside of this operation is that your code must be completely reworked with promise-like chain (because of the http request).

Luckly there is already a library which makes this process much much easier: StackTraceJS , you can give it a try.

This would be its usage (from library docs):

var error = new Error('BOOM!');
StackTrace.fromError(error).then(callback).catch(errback)
/*
==> Promise([
    {functionName: 'fn', fileName: 'file.js', lineNumber: 32, columnNumber: 1},
    {functionName: 'fn2', fileName: 'file.js', lineNumber: 543, columnNumber: 32},
    {functionName: 'fn3', fileName: 'file.js', lineNumber: 8, columnNumber: 1}
], Error)
*/

Side Note

As you stated in the question comment, you are using React, and the usual working pipeline is using wepack or other js bundler to output a single JS file from all the dependencies. During developing you could encounter no problems to find out the file from the errors stack, but in production you could omit sourcemap informations from the bundle or either have some internal/uglified filenames which are not linked with the original file. This means that the behaviour of your code could change between dev/prod configuration depending on your building pipeline.

Theoretical solution

The (proto-OOP) theory states to use prototype to discriminate between Error types in order to filter unwanted behaviours.

  • So first of all you should use a custom class to define the errors thrown by your application/library (see Custom Error - MDN section). By doing so you must throw or extend only your CustomError(s) in your code.

  • Secondly you should filter the error by its type/properties and not by its source file, so (if you can) check what classes of Error the 3rd party function can throw.

In this way it's easy to isolate only those 3rd party errors, you can do just a simple check of inheritance within the try/catch block:

try { /* dangerous code */ }
catch (ex) {
    if (ex instanceof MyError) { /* handle your errors */ }
    else if (ex instanceof The3rdPartyCustomError) { /* handle specific 3rd party CustomError */ }
    else if (ex.__proto__ instanceof Error) { /* handle generic 3rd party CustomErrors */ }
    else { /* handle native errors (or bad practice 3rd party errors) */ }
}

But all of this theory can be difficult to implement, especially because 3rd party libraries very rarely implement their CustomError classes, so you will end up to handle only native errors and your defined classes.

Give it a try and check what kind of errors can throw your 3rd parties libs. Maybe the simpler solution is to filter the erros by Error.message or any other properties which could work better than expected in your domain case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM