One, foreword

Recently, I am very interested in front-end monitoring, so I used Sentry, an excellent open source library in front-end monitoring, and studied the Sentry source code to sort out this series of articles, hoping to help you better understand the principle of front-end monitoring.

In this series of articles, I will explain the whole process in an easy way, combining the API on the official website with the flow chart.

Here are the topics I have completed and plan for my next post:

  • Understand Sentry initialization
  • How Sentry handles error data (this article)
  • How does Sentry report the error when he gets it?

How to debug Sentry source code you can follow the following (my version is: “5.29.2”) :

  • git clone [email protected]:getsentry/sentry-javascript.git
  • Go to Packages/Browser for NPM I download dependencies
  • Then NPM run Build in Packages/Browser to generate build folder
  • Into the packages/browser/examples open index. The HTML can happy debugging (here suggest that use the Live Server open)
  • Note: packages/browser/examples of bundle. Js source after the packaging, he pointed to the packages/browser/build/bundle. Js. You’ll also find bundle.es6.js in the build directory. If you want to read it in es6 mode, you can replace it with bundle.es6.js

Second, the introduction

Let’s take a look at the basics of front-end errors:

2.1 Common types of front-end errors:

(1) ECMAScript Execeptions

Instances of errors that occur while script code is running are thrown. In addition to the general Error constructor, JavaScript has seven other types of Error constructors

  • SyntaxError: An exception raised by the use of an undefined or incorrect syntax. Syntax errors can be detected during compilation or parsing of source code.
  • ReferenceError: ReferenceError represents an error that occurs when a variable that does not exist is referenced.
  • RangeError: a RangeError represents when a value is not in its allowed range or set.
  • TypeError indicates an error that occurs when the type of a value is not the expected type.
  • URIError: URL errors are used to indicate errors caused by using the global URI handler in a wrong way.
  • EvalError: An error in the eval function represents an error about the eval function. This exception is no longer thrown by JavaScript, but the EvalError object remains compatible.
  • InternalError: Engine error indicates an error that occurs inside the JavaScript engine (for example, “InternalError: too much recursion”).

(2) DOMException

The latest DOM specification defines a set of error types, compatible with the old browsing DOMError interface, complete and normalize DOM error types.

  • IndexSizeError: The index is not in the allowed range
  • HierarchyRequestError: The node tree hierarchy is wrong.
  • WrongDocumentError: Object is in the wrong Document.
  • InvalidCharacterError* : String contains invalid characters.
  • NoModificationAllowedError: object cannot be modified.
  • NotFoundError: Object cannot be found.
  • NotSupportedError: the operation is not supported
  • InvalidStateError: The object is an invalid state.
  • SyntaxError: The string does not match the expected pattern.
  • InvalidModificationError: The object cannot be modified in this way.
  • NamespaceError: Operations are not allowed in XML namespaces.
  • InvalidAccessError: The object does not support this operation or argument.
  • TypeMismatchError: The type of the object does not match the expected type.
  • SecurityError: This operation is unsafe.
  • NetworkError: a NetworkError occurs.
  • AbortError: The operation is aborted.
  • URLMismatchError: A given URL does not match another URL.
  • QuotaExceededError: The given quota has been exceeded.
  • TimeoutError: Operation times out.
  • InvalidNodeTypeError: The node for this operation is incorrect or the ancestor is incorrect.
  • DataCloneError: The object cannot be cloned.

2.2 Front-end error exceptions are classified according to capture methods

(1) Script error

Script errors can be classified as compile-time errors and runtime errors

Script errors can be caught by:

  • try catch

    Advantages:

    • By the try… Catch tells us what went wrong, and we also have stack information that tells us what line and column in which file the error occurred.

    Disadvantages:

    • Only exceptions to synchronized code can be caught
    • Catching errors is intrusive and inappropriate as a monitoring system
  • window.onerror

    Advantages:

    • Can catch errors globally

    • Return true at the end of window.onerror so that the browser does not send the error message to the console

      /* * @param MSG {String} : error message * @param url{String} : error script url * @param line{Number} : error code * @param colunm{Number} : Error column * @param error{object} : error object */
      
      window.onerror = function (msg, url, line, colunm, error) {
      	return true;
      }
      Copy the code

    Disadvantages:

    • Catch only immediate runtime errors, not resource load errors (Object. Onerror will terminate after it is captured, so window.onerror cannot catch resource loading errors)

Script Cross domain scripting error capture:

For performance reasons, we usually put the script files in the CDN, which greatly speeds up the first screen time. However, if a Script Error occurs, for security reasons, the browser cannot capture detailed Error information for scripts from different sources and only displays Script Error

Solution:

  • Can be found inscriptTag, addcrossoriginProperty (RecommendedwebpackPlug-ins add automatically).
  • Access-control-origin :*

(2) Resource loading errors

Resource loading errors include: img, script, link, audio, video, iframe…

Resource loading and script error similarities and differences:

Similarities:

  • Monitoring resource errors is essentially the same as monitoring regular script errors, monitoring error events to achieve error capture.

Difference:

  • Script error parameter objectinstanceof ErrorEvent, while resource error parameter objectinstanceof Event. (due toErrorEvent inherits from Event, so whether it’s a script error or a resource error parameter object, theyAll instanceof Event, so, need to determine the script error).
  • The script error parameter object containsmessageResource errors do not.

Resource loading capture mode:

  • Object. onError: For example, img tags and script tags can add onError events to catch resource loading errors

  • Performance. GetEntries: You can obtain the number of resources that are successfully loaded from the website through performance. GetEntries

    Therefore, allIms and loadedImgs can be compared to find the image resource is not loaded items

    const allImgs = document.getElementsByTagName('image')
    const loadedImgs = performance.getEntries().filter(i= > i.initiatorType === 'img')
    Copy the code

(3) The Promise is wrong

The above try catch and window. onError do not catch a Promise error (because it is asynchronous), and when a Promise is rejected and there is no Reject handler, The unhandledrejection event is raised. When a Promise is rejected and a Reject handler is present, the rejectionHandled event is raised. Description: Sentry side collection is not only reject errors that window. Unhandledrejection

2.3 Error Reporting Method

  • Reporting using Ajax communication (Sentry style)

  • The IMG request is reported, and the URL parameter contains error information

    SRC = ‘docs. Sentry. IO /error? The error… ‘

Three, how to monitor errors

We can look at the picture below, this section will focus on such as explained in the Sentry initialization, how to monitor the wrong, to the window. The onerror and window unhandledrejection all did what

About Sentry initialization integration content can refer to play front-end monitoring, comprehensive analysis of Sentry source code (a) | understand Sentry initialization

Monitoring error steps are as follows:

3.1 the initialization

The user uses sentry. init to initialize, passing in parameters

3.2 Binding a Control center Hub

Init calls initAndBind to initialize the client and bind the client to the hub. The bindClient method in initAndBind binds the client to the hub control center

3.3 integrated install

Focus on the Set Integrations method in bindClient

      bindClient(client) {
          const top = this.getStackTop();
          top.client = client;
          if(client && client.setupIntegrations) { client.setupIntegrations(); }}Copy the code

setupIntegrations

     setupIntegrations() {
          if (this._isEnabled()) {
              this._integrations = setupIntegrations(this._options); }}function setupIntegrations(options) {
        const integrations = {};
        getIntegrationsToSetup(options).forEach(integration= > {
            integrations[integration.name] = integration;
            setupIntegration(integration);
        });
        return integrations;
    }
Copy the code

Analysis:

  • SetupIntegrations iterates through integrations and installs default or custom integrations
  • GetIntegrationsToSetup is just get integrations
  • Note: _isEnabled is an option passed in by the user. You cannot set enabled to false and DSN to null, otherwise the data will not be collected and sent

3.4 setupOnce

Then look at setupIntegration

  /** Setup given integration */
  function setupIntegration(integration) {
      if(installedIntegrations.indexOf(integration.name) ! = = -1) {
          return;
      }
      integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
      installedIntegrations.push(integration.name);
      logger.log(`Integration installed: ${integration.name}`);
  }
Copy the code

Analysis:

  • Here the integration is traversed, executing the setupOnce method
  • There are seven by default, and we just have to focus on the New GlobalHandlers
  • When a GlobalHandlers install is installed, its setupOnce method is executed

setupOnce

    setupOnce() {
      Error.stackTraceLimit = 50;
      if (this._options.onerror) {
        logger.log('Global Handler attached: onerror');
        this._installGlobalOnErrorHandler();
      }
      if (this._options.onunhandledrejection) {
        logger.log('Global Handler attached: onunhandledrejection');
        this._installGlobalOnUnhandledRejectionHandler(); }}Copy the code

Analysis:

  • _options is set at new GlobalHandlers()Onerror and onunHandledrejection default to true

From here you can see that Sentry handles run errors and Promise errors differently

3.5 the window. The onerror

_installGlobalOnErrorHandler corresponding to the window. The onerror type

      _installGlobalOnErrorHandler() {
          if (this._onErrorHandlerInstalled) {
              return;
          }
          addInstrumentationHandler({
              callback: (data) = > {
              	// ...
              },
              type: 'error'});this._onErrorHandlerInstalled = true;
      }
Copy the code

Analysis:

  • _installGlobalOnErrorHandler for incoming addInstrumentationHandler methodcallbackandtype
  • The callback method has nothing to do with initialization, which we’ll focus on in the next chapter.
  • Type Indicates the type forDistinguish current typeIn _installGlobalOnUnhandledRejectionHandler will pass into the type: ‘unhandledrejection’

3.6 the window unhandledrejection

_installGlobalOnUnhandledRejectionHandler corresponding window. Unhandledrejection

      _installGlobalOnUnhandledRejectionHandler() {
          if (this._onUnhandledRejectionHandlerInstalled) {
              return;
          }
          addInstrumentationHandler({
              callback: (e) = > {
            		// ...
              },
              type: 'unhandledrejection'});this._onUnhandledRejectionHandlerInstalled = true;
      }
Copy the code

With _installGlobalOnErrorHandler similarly

3.7 addInstrumentationHandler

To see the general addInstrumentationHandler method

  /**
   * Add handler that will be called when given type of instrumentation triggers.
   * Use at your own risk, this might break without changelog notice, only used internally.
   * @hidden* /
  const handlers = {};
  function addInstrumentationHandler(handler) {
      if(! handler ||typeofhandler.type ! = ='string' || typeofhandler.callback ! = ='function') 			{
          return;
      }
      handlers[handler.type] = handlers[handler.type] || [];
      handlers[handler.type].push(handler.callback);
      instrument(handler.type);
  }
Copy the code

Analysis:

  • AddInstrumentationHandler the type and the callback passed to handlers,When an error occurs, the corresponding callback method is executed.
  • AddInstrumentationHandler will endCall the instrument method based on type typeTo implement various methods.

3.8 instrument

Instrument lets you know what cases Sentry handles. If you are interested in other aspects, you can view the corresponding source code, which I will also open a special topic after part of the content.

  function instrument(type) {
    if (instrumented[type]) {
      return;
    }
    instrumented[type] = true;
    switch (type) {
      case 'console':
        instrumentConsole();
        break;
      case 'dom':
        instrumentDOM();
        break;
      case 'xhr':
        instrumentXHR();
        break;
      case 'fetch':
        instrumentFetch();
        break;
      case 'history':
        instrumentHistory();
        break;
      case 'error':
        instrumentError();
        break;
      case 'unhandledrejection':
        instrumentUnhandledRejection();
        break;
      default:
        logger.warn('unknown instrumentation type:', type); }}Copy the code

3.9 Key points: instrumentError

  let _oldOnErrorHandler = null;
  /** JSDoc */
  function instrumentError() {
      _oldOnErrorHandler = global$2.onerror;
      global$2.onerror = function (msg, url, line, column, error) {
          triggerHandlers('error', {
              column,
              error,
              line,
              msg,
              url,
          });
          if (_oldOnErrorHandler) {
              return _oldOnErrorHandler.apply(this.arguments);
          }
          return false;
      };
  }
Copy the code

Analysis:

  • If you are familiar withAop (aspect oriented programming)Winodw.onerror has been hijacked to add the triggerHandlers method.
  • And when we listen for onError, we call the triggerHandlers method based on type ‘error’handlersFind the corresponding types of callback methods, namely _installGlobalOnErrorHandler callback methods

Key points: 3.10 instrumentUnhandledRejection.

  function instrumentUnhandledRejection() {
      _oldOnUnhandledRejectionHandler = global$2.onunhandledrejection;
      global$2.onunhandledrejection = function (e) {
          triggerHandlers('unhandledrejection', e);
          if (_oldOnUnhandledRejectionHandler) {
              // eslint-disable-next-line prefer-rest-params
              return _oldOnUnhandledRejectionHandler.apply(this.arguments);
          }
          return true;
      };
  }
Copy the code

With instrumentError similarly


At this point, you have completed the entire initialization step. The next step is to focus on the different outputs for each type of error.

What does Sentry do with error messages

Now let’s first take a look at _installGlobalOnErrorHandler and _installGlobalOnUnhandledRejectionHandler callback

_installGlobalOnErrorHandler:

 callback: data= > {
          const error = data.error;
          const currentHub = getCurrentHub();
          const hasIntegration = currentHub.getIntegration(GlobalHandlers);
          const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
          if(! hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {return;
          }
          const client = currentHub.getClient();
          const event = isPrimitive(error)
            ? this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)
            : this._enhanceEventWithInitialFrame(
                eventFromUnknownInput(error, undefined, {
                  attachStacktrace: client && client.getOptions().attachStacktrace,
                  rejection: false,
                }),
                data.url,
                data.line,
                data.column,
              );
          addExceptionMechanism(event, {
            handled: false.type: 'onerror'}); currentHub.captureEvent(event, {originalException: error,
          });
        },
Copy the code

_installGlobalOnUnhandledRejectionHandler

        callback: e= > {
          let error = e;
      	// ...
          const currentHub = getCurrentHub();
          const hasIntegration = currentHub.getIntegration(GlobalHandlers);
          const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
          if(! hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {return true;
          }
          const client = currentHub.getClient();
          const event = isPrimitive(error)
            ? this._eventFromRejectionWithPrimitive(error)
            : eventFromUnknownInput(error, undefined, {
                attachStacktrace: client && client.getOptions().attachStacktrace,
                rejection: true}); event.level =exports.Severity.Error;
          addExceptionMechanism(event, {
            handled: false.type: 'onunhandledrejection'}); currentHub.captureEvent(event, {originalException: error,
          });
          return;
        },
Copy the code

From the source code looks _installGlobalOnErrorHandler and _installGlobalOnUnhandledRejectionHandler are very similar, we unified analysis together.

4.1 shouldIgnoreOnError

Please pay attention to this function, let’s have a look at the source:

 let ignoreOnError = 0;
  function shouldIgnoreOnError() {
    return ignoreOnError > 0;
  }
Copy the code

Analysis:

  • ShouldIgnoreOnError should return true if you throw a new Error directly

  • InstrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM

  • As a result, function is initialized with a wrap method, so ignoreOnError +1 when reporting an error. This is why callback is not reported

    try{... }catch{
    	ignoreNextOnError()
    }
    
    ------------------------------------------
      function ignoreNextOnError() {
        // onerror should trigger before setTimeout
        ignoreOnError += 1;
        setTimeout(() = > {
          ignoreOnError -= 1;
        });
      }
    Copy the code

4.2 _installGlobalOnErrorHandlerwith_installGlobalOnUnhandledRejectionHandlerThe difference between

Although these two functions may look a lot, they are almost the same, except for error handling:

_installGlobalOnErrorHandler

          const event = isPrimitive(error)
            ? this._eventFromIncompleteOnError(...)
            : this._enhanceEventWithInitialFrame( eventFromUnknownInput(...) )Copy the code

_installGlobalOnUnhandledRejectionHandler

          const event = isPrimitive(error)
            ? this._eventFromRejectionWithPrimitive(...) : eventFromUnknownInput(...) ;Copy the code

Analysis:

  • throughisPrimitiveIt takes different actions to determine whether an error type is a primitive type or a reference type

So we divided it into four modules:

  • eventFromUnknownInput
  • _enhanceEventWithInitialFrame.
  • _eventFromIncompleteOnError.
  • _eventFromRejectionWithPrimitive

To examine what is done to the error:

4.3 Important: eventFromUnknownInput

eventFromUnknownInput(error, undefined, {
  attachStacktrace: client && client.getOptions().attachStacktrace,
  rejection: false,}) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromUnknownInput(exception, syntheticException, options = {}) {
    let event;
    if (isErrorEvent(exception) && exception.error) {
      // If it is an ErrorEvent with `error` property, extract it to get actual Error
      const errorEvent = exception;
      // eslint-disable-next-line no-param-reassign
      exception = errorEvent.error;
      event = eventFromStacktrace(computeStackTrace(exception));
      return event;
    }
    if (isDOMError(exception) || isDOMException(exception)) {
      // If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
      // then we just extract the name, code, and message, as they don't provide anything else
      // https://developer.mozilla.org/en-US/docs/Web/API/DOMError
      // https://developer.mozilla.org/en-US/docs/Web/API/DOMException
      const domException = exception;
      const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
      const message = domException.message ? `${name}: ${domException.message}` : name;
      event = eventFromString(message, syntheticException, options);
      addExceptionTypeValue(event, message);
      if ('code' in domException) {
        event.tags = Object.assign(Object.assign({}, event.tags), { 'DOMException.code': `${domException.code}` });
      }
      return event;
    }
    if (isError(exception)) {
      // we have a real Error object, do nothing
      event = eventFromStacktrace(computeStackTrace(exception));
      return event;
    }
    if (isPlainObject(exception) || isEvent(exception)) {
      // If it is plain Object or Event, serialize it manually and extract options
      // This will allow us to group events based on top-level keys
      // which is much better than creating new group when any key/value change
      const objectException = exception;
      event = eventFromPlainObject(objectException, syntheticException, options.rejection);
      addExceptionMechanism(event, {
        synthetic: true});return event;
    }
    // If none of previous checks were valid, then it means that it's not:
    // - an instance of DOMError
    // - an instance of DOMException
    // - an instance of Event
    // - an instance of Error
    // - a valid ErrorEvent (one with an error property)
    // - a plain Object
    //
    // So bail out and capture it as a simple message:
    event = eventFromString(exception, syntheticException, options);
    addExceptionTypeValue(event, `${exception}`.undefined);
    addExceptionMechanism(event, {
      synthetic: true});return event;
  }
Copy the code

Analysis:

  • Parameter Description:

    • Exception is an error message
    • Notice that the second argument is undefindSyntheticException passed in eventFromPlainObject and eventFromString is undefined!! No error stack is generated.
    • The third parameter attachStacktrace here isThe user set attachStacktrace in sentry.initOn behalf ofYou need to trace the error stack
  • IsErrorEvent if the error type isErrorEvent eventFromStacktrace(computeStackTrace(exception))

      function isErrorEvent(wat) {
        return Object.prototype.toString.call(wat) === '[object ErrorEvent]';
      }
    Copy the code
  • IsDOMError for DOMError (deprecated), isDOMException for DOMException, call eventFromString(note that the second argument syntheticException is null)

      function isDOMError(wat) {
        return Object.prototype.toString.call(wat) === '[object DOMError]';
      }
      
       function isDOMException(wat) {
        return Object.prototype.toString.call(wat) === '[object DOMException]';
      }
    Copy the code
  • IsError, Error or Exception or DOMException will go here, will go eventFromStacktrace(computeStackTrace(Exception))

      function isError(wat) {
        switch (Object.prototype.toString.call(wat)) {
          case '[object Error]':
            return true;
          case '[object Exception]':
            return true;
          case '[object DOMException]':
            return true;
          default:
            return isInstanceOf(wat, Error); }}Copy the code
  • IsPlainObject or isEvent for ordinary messages will take eventFromPlainObject(note that the second argument syntheticException is null)

      function isEvent(wat) {
        return typeofEvent ! = ='undefined' && isInstanceOf(wat, Event);
      }
    
      function isPlainObject(wat) {
        return Object.prototype.toString.call(wat) === '[object Object]';
      }
    Copy the code
  • The rest is treated as simple messages

In general, handling eventFromStacktrace, eventFromPlainObject, and eventFromString is all about getting an error message for further data processing.

ComputeStackTrace is the key to erase differences and generate error stack

(1) Key: computeStackTrace gets the error stack

ComputeStackTrace builds on some of the processing methods in TraceKit, primarily by smoothing out the error stack differences between browsers

  function computeStackTrace(ex) {
      let stack = null;
      let popSize = 0;
  	// ...
      try {
          stack = computeStackTraceFromStackProp(ex);
          if (stack) {
              returnpopFrames(stack, popSize); }}catch (e) {
          // no-empty
      }
      return {
          message: extractMessage(ex),
          name: ex && ex.name,
          stack: [].failed: true}; }Copy the code

computeStackTraceFromStackProp

Concrete implementation of smoothing out browser differences

  function computeStackTraceFromStackProp(ex) {
    if(! ex || ! ex.stack) {return null;
    }
    const stack = [];
    const lines = ex.stack.split('\n');
    let isEval;
    let submatch;
    let parts;
    let element;
    for (let i = 0; i < lines.length; ++i) {
      if ((parts = chrome.exec(lines[i]))) {
        const isNative = parts[2] && parts[2].indexOf('native') = = =0; // start of line
        isEval = parts[2] && parts[2].indexOf('eval') = = =0; // start of line
        if (isEval && (submatch = chromeEval.exec(parts[2))) {// throw out eval line/column and use top-most line/column number
          parts[2] = submatch[1]; // url
          parts[3] = submatch[2]; // line
          parts[4] = submatch[3]; // column
        }
        element = {
          // working with the regexp above is super painful. it is quite a hack, but just stripping the `address at `
          // prefix here seems like the quickest solution for now.
          url: parts[2] && parts[2].indexOf('address at ') = = =0 ? parts[2].substr('address at '.length) : parts[2].func: parts[1] || UNKNOWN_FUNCTION,
          args: isNative ? [parts[2]] : [],
          line: parts[3]? +parts[3] : null.column: parts[4]? +parts[4] : null}; }else if ((parts = winjs.exec(lines[i]))) {
        element = {
          url: parts[2].func: parts[1] || UNKNOWN_FUNCTION,
          args: [].line: +parts[3].column: parts[4]? +parts[4] : null}; }else if ((parts = gecko.exec(lines[i]))) {
        isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
        if (isEval && (submatch = geckoEval.exec(parts[3))) {// throw out eval line/column and use top-most line number
          parts[1] = parts[1] | |`eval`;
          parts[3] = submatch[1];
          parts[4] = submatch[2];
          parts[5] = ' '; // no column when eval
        } else if (i === 0 && !parts[5] && ex.columnNumber ! = =void 0) {
          // FireFox uses this awesome columnNumber property for its top frame
          // Also note, Firefox's column number is 0-based and everything else expects 1-based,
          // so adding 1
          // NOTE: this hack doesn't work if top-most frame is eval
          stack[0].column = ex.columnNumber + 1;
        }
        element = {
          url: parts[3].func: parts[1] || UNKNOWN_FUNCTION,
          args: parts[2]? parts[2].split(', ') : [],
          line: parts[4]? +parts[4] : null.column: parts[5]? +parts[5] : null}; }else {
        continue;
      }
      if(! element.func && element.line) { element.func = UNKNOWN_FUNCTION; } stack.push(element); }if(! stack.length) {return null;
    }
    return {
      message: extractMessage(ex),
      name: ex.name,
      stack,
    };
  }
Copy the code

Analysis:

  • Ex. Stack allows you to get the current error stack.

    Let me give you an example of a mistake. The error stack obtained is:

    Error: externalLibrary method broken: 1610359373199
    at externalLibrary (https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js:2 9) :
    at https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js:5 : 1."
    Copy the code
  • Split (‘\n ‘) to an array. We need to iterate over each Error stack because, for example, ‘Error: externalLibrary method broken: 1610359373199’ is the same browser and can be skipped

    [
    	'Error: externalLibrary method broken: 1610359373199'.'at externalLibrary (https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js:2:9)'.'at https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-li b.js:5:1" '
    ]
    Copy the code
  • It then uses the re to determine which browser kernel the current error belongs to, and then does different processing to smooth out the differences

  • Finally, the stack is returned with args, line, func, URL data format

    args: []
    column: 9
    func: "externalLibrary"
    line: 2
    url: "https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js"
    Copy the code
(2) eventFromStacktrace, eventFromString and eventFromPlainObject

All three of these are explained in a unified way because the principle is the same, you take the error stack and so on and process it further,

Note that both eventFromString and syntheticException passed in by eventFromPlainObject are null so there is no error stack

Take a look at their source code:

  
  function eventFromStacktrace(stacktrace) {
    const exception = exceptionFromStacktrace(stacktrace);
    return {
      exception: {
        values: [exception], }, }; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromString(input, syntheticException, options = {}) 	{
    const event = {
      message: input,
    };
    if (options.attachStacktrace && syntheticException) {
      const stacktrace = computeStackTrace(syntheticException);
      const frames = prepareFramesForEvent(stacktrace.stack);
      event.stacktrace = {
        frames,
      };
    }
    returnevent; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -function eventFromPlainObject(exception, syntheticException, rejection) {
    const event = {
      exception: {
        values: [{type: isEvent(exception) ? exception.constructor.name : rejection ? 'UnhandledRejection' : 'Error'.value: `Non-Error ${
              rejection ? 'promise rejection' : 'exception'
            } captured with keys: ${extractExceptionKeysForMessage(exception)}`,}]},extra: {
        __serialized__: normalizeToSize(exception),
      },
    };
    if (syntheticException) {
      const stacktrace = computeStackTrace(syntheticException);
      const frames = prepareFramesForEvent(stacktrace.stack);
      event.stacktrace = {
        frames,
      };
    }
    return event;
  }

Copy the code

Analysis:

  • ComputeStackTrace is what we talked about above, smoothing out differences and getting the error stack

  • Exception contains error types and information

  • ExceptionFromStacktrace actually calls prepareFramesForEvent to get the error stack

  • Take a look at the data returned

      exception:  {
          stacktrace: [{colno: 1.filename:
              'https://rawgit.com/kamilogorek/cfbe9f92196c6c61053e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js'.function: '? '.in_app: true.lineno: 5}, {colno: 9.filename:
                'https://rawgit.com/kamilogorek/cfbe9f92196c6c610535e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js'.function: 'externalLibrary'.in_app: true.lineno: 2,},]; type:'Error';
          value: 'externalLibrary method broken: 1610364003791';
        }
    Copy the code
(3) Summary

EventFromUnknownInput is a lot of code, but very clear.

Follow one of the methods eventFromStacktrace, eventFromString, and eventFromPlainObject based on the type of error message. For example, if the error type is DOMError, DOMException or ordinary object, there is no error stack message, while others will use computeStackTrace to erase the differences between different browsers, obtain the error stack, and finally return a unified structure for error data processing.

4.4 _enhanceEventWithInitialFrame

    _enhanceEventWithInitialFrame(event, url, line, column) {
      event.exception = event.exception || {};
      event.exception.values = event.exception.values || [];
      event.exception.values[0] = event.exception.values[0) | | {}; event.exception.values[0].stacktrace = event.exception.values[0].stacktrace || {};
      event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames || [];
      const colno = isNaN(parseInt(column, 10))?undefined : column;
      const lineno = isNaN(parseInt(line, 10))?undefined : line;
      const filename = isString(url) && url.length > 0 ? url : getLocationHref();
      if (event.exception.values[0].stacktrace.frames.length === 0) {
        event.exception.values[0].stacktrace.frames.push({
          colno,
          filename,
          function: '? '.in_app: true,
          lineno,
        });
      }
      returnevent; }}Copy the code

Analysis:

  • Through eventFromUnknownInput get event, url, the line, call _enhanceEventWithInitialFrame after colom
  • _enhanceEventWithInitialFrame ensure exception, values, stacktrace, frames set have default values

4.5 _eventFromRejectionWithPrimitive

    _eventFromRejectionWithPrimitive(reason) {
      return {
        exception: {
          values: [{type: 'UnhandledRejection'.// String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
              value: `Non-Error promise rejection captured with value: The ${String(reason)}`,},],},}; }Copy the code

Analysis:

  • The code simply returns the default format

4.6 _eventFromIncompleteOnError

this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)
--------------------------------------------------------------------------------------
    /** * This function creates a stack from an old, error-less onerror handler. */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    _eventFromIncompleteOnError(msg, url, line, column) {
      const ERROR_TYPES_RE = / ^ (? :[Uu]ncaught (? :exception: )?) ? (? : ((? :Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )? (.*)$/i;
      // If 'message' is ErrorEvent, get real message from inside
      let message = isErrorEvent(msg) ? msg.message : msg;
      let name;
      if (isString(message)) {
        const groups = message.match(ERROR_TYPES_RE);
        if (groups) {
          name = groups[1];
          message = groups[2]; }}const event = {
        exception: {
          values: [{type: name || 'Error'.value: message,
            },
          ],
        },
      };
      return this._enhanceEventWithInitialFrame(event, url, line, column); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -_enhanceEventWithInitialFrame(event, url, line, column) {
      event.exception = event.exception || {};
      event.exception.values = event.exception.values || [];
      event.exception.values[0] = event.exception.values[0) | | {}; event.exception.values[0].stacktrace = event.exception.values[0].stacktrace || {};
      event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames || [];
      const colno = isNaN(parseInt(column, 10))?undefined : column;
      const lineno = isNaN(parseInt(line, 10))?undefined : line;
      const filename = isString(url) && url.length > 0 ? url : getLocationHref();
      if (event.exception.values[0].stacktrace.frames.length === 0) {
        event.exception.values[0].stacktrace.frames.push({
          colno,
          filename,
          function: '? '.in_app: true,
          lineno,
        });
      }
      returnevent; }}Copy the code

Analysis:

  • _eventFromIncompleteOnError for basic types of errors, which can be directly obtained error messages, so were direct incoming url parameter, in line and column
  • _eventFromIncompleteOnError isAdd error types and information
  • _enhanceEventWithInitialFrame isAdding an error stack

At this point, window.onerror and window.unhandrerejection have been studied for different handling of errors, and the next step is to finally conduct unified processing of error data.

4.7 addExceptionMechanis

AddExceptionMechanism Adds the Mechanism attribute to the Event event

For example, the above example is processed through addExceptionMechanism

addExceptionMechanism(event, {
  handled: false.type: 'onerror'});Copy the code

The following data formats are obtained:

 exception: {
      stacktrace: [
     	// ...].type: 'Error'.value: 'externalLibrary method broken: 1610364003791'.mechanism: {type:'onerror'.handled:false}}Copy the code

4.8 currentHub.captureEvent

At this point, we have converted the error message into the data format we want.

CurrentHub. CaptureEvent is used to report data, which will be used in the next article to analyze Sentry source code (3).

4.9 Supplementary: How to Handle the Error Information Proactively reported

Except by listening to the window. The window and onerror. Unhandledrejection, report users can also use captureException active fault data

Now let’s see how this is different from handling data when listening

 function captureException(exception, hint, scope) {
		// ...
      this._process(
        this._getBackend()
          .eventFromException(exception, hint)
         // ...
      );
      returneventId; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromException(options, exception, hint) {
    const syntheticException = (hint && hint.syntheticException) || undefined;
    const event = eventFromUnknownInput(exception, syntheticException, {
      attachStacktrace: options.attachStacktrace,
    });
    addExceptionMechanism(event, {
      handled: true.type: 'generic'});// ...
  }
Copy the code

Analysis:

  • You can see that the eventFromException method is actively reported
  • EventFromException is actually a call4.3 eventFromUnknownInput
  • Note that syntheticException is not undeFind and can trace the error stack

4.10 summarize

Finally, let’s go through a flow chart to sort out what Sentry does when something goes wrong.

Five, the actual combat

This section gives you an intuitive view of the types of data returned by Sentry through several examples

Sentry. Init configuration

Sentry.init({
  dsn: '.. '.attachStacktrace: true.debug: true.enabled: true.release: '.. '});Copy the code

5.1 window. The error | error type is the basic type

Here go window. In the onerror 4.6 _eventFromIncompleteOnError method to process the data

Test cases:

    <button id="error-string">window.error | string Error</button>
  <script>
  document.querySelector('#error-string').addEventListener('click'.() = > {
    const script = document.createElement('script');
    script.src = './errorString.js';
    document.body.appendChild(script);
  });
  </script>
Copy the code

errorString.js:

function externalLibrary(date) {
  throw `string Error${date}`;
}

externalLibrary(Date.now());

Copy the code

Show results:

Corresponding error data:

      exception: {
        values: {
          mechanism: {
            handled: false.type: 'onerror',},stacktrace: {
            frames: [{colno: 3.filename: 'http://127.0.0.1:5500/packages/browser/examples/errorString.js'.function: '? '.in_app: true.lineno: 2,}]},type: 'Error'.value: 'string Error1610594068361',}},Copy the code

5.2 the window. The error | error type is a reference type

Here go window. In the onerror 4.4 _enhanceEventWithInitialFrame method to process the data, due to walk the eventFromPlainObject eventFromUnknownInput, So there’s no error stack message in there.

Test cases:

  <button id="error-throw-new-error">window.error | throw new Error</button>
  <script>
  document.querySelector('#error-throw-new-error').addEventListener('click'.() = > {
    console.log('click');
    const script = document.createElement('script');
    script.crossOrigin = 'anonymous';
    script.src =  'https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js';
    document.body.appendChild(script);
  });
  </script>
Copy the code

external-lib.js

function externalLibrary (date) {
  throw new Error(`externalLibrary method broken: ${date}`);
}
externalLibrary(Date.now());
Copy the code

Show results:

Corresponding error data:

    {
      exception: {
        values: [{stacktrace: {
              mechanism: {
                handled: false.type: 'onunhandledrejection',},frames: [{colno: 23.filename: 'http://127.0.0.1:5500/packages/browser/examples/app.js'.function: '? '.in_app: true.lineno: 171,},],},},]; }type: 'Error';
      value: 'promise-throw-error';
    }
Copy the code

5.3 window. The error | not reporting errors

ShouldIgnoreOnError 4.1 shouldIgnoreOnError 4.1 shouldIgnoreOnError 4.1 shouldIgnoreOnError

The test code

    <button id="ignore-message">ignoreError message example</button>
   <script>
     document.querySelector('#ignore-message').addEventListener('click'.() = > {
    	fun()
  });
  </script>
Copy the code

Show results:

5.4 Windows. Unhandledrejection | error type is the basic type

Here go window. In the unhandledrejection 4.5 _eventFromRejectionWithPrimitive method to process the data, there is no news of the error stack.

The test code

   <button id="unhandledrejection-string">window.unhandledrejection | string-error</button>
    <script>
  document.querySelector('#unhandledrejection-string').addEventListener('click'.() = > {
    new Promise(function(resolve, reject) {
      setTimeout(function() {
        return reject('oh string error');
      }, 200);
    });
  });
  </script>
Copy the code

Show results:

Corresponding error data:

      exception: {
        values: [{mechanism: { handled: false.type: 'onunhandledrejection'}},].type: 'UnhandledRejection'.value: 'Non-Error promise rejection captured with value: oh string error'.level: 'error',}Copy the code

5.5 Windows. Unhandledrejection | error type is a reference type

Here go window. In the unhandledrejection 4.3 key: the eventFromPlainObject eventFromUnknownInput, so there is no news of the error stack.

The test code

    <button id="unhandledrejection-plainObject">window.unhandledrejection | plainObject</button>
    <script>
  document.querySelector('#unhandledrejection-plainObject').addEventListener('click'.() = > {
    new Promise(function(resolve, reject) {
      setTimeout(function() {
        const obj = {
          msg: 'plainObject'.testObj: {
            message: 'This is a test.',},date: new Date(),
          reg: new RegExp(),
          testFun: () = > {
            console.log('testFun'); }};return reject(obj);
      }, 200);
    });
  });
  </script>
Copy the code

Show results:

Corresponding error data:

      exception: {
        values: [{type: 'UnhandledRejection'.value: '"Non-Error promise rejection captured with keys: msg, testFun, testObj"'],},extra: {
          __serialized__: {
            msg: 'plainObject'.testFun: '[Function: testFun]'.testObj: {
              message: 'This is a test.',}}},Copy the code

5.5 Windows. Unhandledrejection | error type is a reference type and is DOMException

Here go window. The unhandledrejection 4.3 key: The eventFromUnknownInput method handles the data, so there is no error stack message in the eventFromString.

The test code

<button id="DOMException">DOMException</button>
<script>
  document.querySelector('#DOMException').addEventListener('click'.() = > {
    screen.orientation.lock('portrait');
  });
</script>
Copy the code

Show results:

Corresponding error data:

      exception: {
        values: [{type: 'Error'.value: 'NotSupportedError: screen.orientation.lock() is not available on this device.',}]},message: 'NotSupportedError: screen.orientation.lock() is not available on this device.'.tags: {
        'DOMException.code': '9',}Copy the code

5.5 Actively Reporting

How to deal with the error information that is actively reported here

The test code

<button id="DOMException">DOMException</button>
<script>
  document.querySelector('#capture-exception').addEventListener('click'.() = > {
    Sentry.captureException(new Error(`captureException call no. The ${Date.now()}`));
  });
</script>
Copy the code

Show results:

The console doesn’t have any errors because sentry. captureException sends an error data to the client

Corresponding error data:

     exception: {
        event_id: '0e7c9eb8fa3c42b786543317943e9d0d'.exception: {
          values: [{mechanism: {
                handled: true.type: 'generic',},stacktrace: {
                frames: [{colno: 29.filename: 'http://127.0.0.1:5500/packages/browser/examples/app.js'.function: 'HTMLButtonElement.<anonymous>'.in_app: true.lineno: 142,},],},},],type: 'Error'.value: 'captureException call no. 1610599823126',}}Copy the code

Six, summarized

Sentry at the time of initialization of the window. The onerror and window unhandledrejection hijacking, when the error occurred, ComputeStackTrace is used to erase the difference between the information returned by each browser kernel to obtain the error stack. Different types of error messages have their own processing methods, and finally output unified error data.

After Sentr, we get non-error data such as user-Agent, browser information, system information, custom information, and so on, and pass it to the Sentry lifecycle function. Finally, we send the data to the Sentry server for error display.

Vii. Reference materials

  • The Sentry’s official website
  • The Sentry warehouse
  • Error MDN
  • ErrorEvent MDN
  • DOMException MDN
  • Front-end anomaly monitoring. – This is enough
  • Talk about front-end monitoring – Error monitoring