0

Following this other question about tracking errors in JS [1], I'm trying to use something similar to window.onerror to track only the errors pertinent to the context of my class or Web Component, since my components are meant to be used in other websites.

Is that possible right now?

(If there are any services that do this right now, it would also be nice to know.)

For example, I would like to track all the errors coming from my class or its inheritors:

abstract class Trackable extends HTMLElement {
  override connectedCallback() {
    this.onerror = (
      message?: string,
      file?: string,
      line?: number,
      column?: number,
      error?: Error
    ) => {
      console.log(error.cause);
    };
  }
}

However, this doesn't seem work. And if I use window instead of this, then any errors on the window will run into this listener, which is not what I want.


References

  1. How do we track Javascript errors? Do the existing tools actually work?
  2. Why does onerror not intercept exceptions from promises and async functions
Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76
  • No, that's not possible. How do you even define the "context of your class"? If you say something like "exceptions where code from my class is found in the stack trace", then just filter all exceptions for just that. – Bergi May 19 '23 at 15:01
  • To me, I feel like a programming language should be capable of detecting if the error was thrown based on a call from within a class. That's similar to what you're mentioning about filtering through the stack trace, however, since we have JS bundlers that change the name of classes in the end, this might be more difficult or unreliable. – Philippe Fanaro May 19 '23 at 17:25
  • 1
    You can wrap all your class methods in `try`/`catch` blocks (even programmatically, using aspect-oriented programming approaches) if that is what you are after. – Bergi May 19 '23 at 17:48
  • and the stack trace is in ``new Error().stack`` – Danny '365CSI' Engelman May 26 '23 at 08:52
  • @Bergi could you maybe exemplify that with an answer? – Philippe Fanaro May 26 '23 at 13:25

2 Answers2

0

As mentioned in one of the comments above, you could wrap all functions in a try/catch to watch for errors and track them. There's a couple ways you could go about this programmatically, but there's some pitfalls to them.

The first option would be to take all the class functions in the constructor and re-assign them with a new, wrapped version of the function. There's a couple ways to do this, one where you explicitly do each function, and another where you iterate over all the keys and then wrap them, something along the lines of this:

function wrapFunction<T extends Function>(original: T) {
  return ((...args: any[]) => {
    try {
      return original(...args);
    } catch (e) {
      // Do you logging here
      // Then re-throw the error so that it can still be handled by consumers
      throw e;
    }
  }) as unknown as T;
}

class MyClass {
  constructor() {
    // Iterate over all the keys, 
    for(const key in this) {
      const value = this[key];
      if(value !== "function") return;
      // Wrap all the functions and assign them
      this[key] = wrapFunction(value as Function) as any;
    }
  }

  someFunction() {
    throw new Error("Test error");
  }
}

The downside here is that this is a little hard to type if you're using TS, and it can cause issues if there's things you don't want to wrap, or if people extend the class and do weird things with it (This can also end up attempting to wrap things that aren't actually class methods).

Another option is to use the new decorators to achieve the same wrapping, but this requires transpilation as decorators aren't at full support in most places yet. This allows you more control over which functions get wrapped and which ones don't, but again has issues if a consumer of your library extends or does weird stuff with your class.

The last option is to manually add that try/catch yourself to all the functions you want logged.

Rodentman87
  • 640
  • 7
  • 14
  • What about the solution in which a base class uses the stack trace to track errors (with the `error` and `unhandleexception` events)? Do you think it's feasible? If so, how would it work? What are its drawbacks? – Philippe Fanaro May 28 '23 at 11:42
  • a) no reason to do this in the constructor, rather you should wrap your prototype methods b) since you're dealing with methods, you must not forget to forward the `this` value c) with generics, utility types like `Parameters` or `ReturnType`, and `keyof MyClass`, you can actually type the wrapper well enough – Bergi May 28 '23 at 16:11
0

you could use Reflect with js proxy to trap most of the things you need. E.g

let handler = {
  get: (target, name, receiver) => {
    return (...args) => {
      return Reflect
        .get(target, name, receiver)
        .apply(target, args)
        .catch((e) => { console.log('caught exception', e) })
    }
  }
}
myComponent = new Proxy(myComponent, handler)

works with async thrown errors aswell Keep in mind that it does add the handle overhead as it'll trigger the handler everytime data is read. you could use the other traps though that could better meet you use case

zergski
  • 793
  • 3
  • 10