We usually collect errors. Most people and companies use Sentry to collect errors. Of course, many companies also have their own front-end monitoring. Recently, our group is going to access Sentry and do a sharing according to our own research. So the following is summarized. I want to share it with you guys.

1. Reporting method

  1. Automatically collect

    • Sensorless collection
  2. Manual reporting is divided into:

     Sentry.captureException('captureException');
     Sentry.captureMessage('captureMessage');
     // Set user information:Scope. SetUser ({" email ":" [email protected] "})// Define a label for an event:Scope.settags ({' API ', 'API/list/get'})// Set the severity of the event:Scope. SetLevel (" error ")// Set additional data:Scope. SetExtra (' data ', {request: { a: 1.b: 2 })
     // Add a crumb
     Sentry.addBreadcrumb({
         category: "auth".message: "Authenticated user ".level: Sentry.Severity.Info,
     });
     // Add a scope title?? The current transaction name is used to group transactions in the Performance product and annotate error events with error points.
     Sentry.configureScope(scope= > scope.setTransactionName("UserListView"));
     / / local
     Sentry.withScope(function (scope) {
         scope.setTag("my-tag"."my value");
         scope.setLevel("warning");
         // will be tagged with my-tag="my value"
         Sentry.captureException(new Error("my error"));
     });
     // Set the context
     Sentry.setContext("character", {
         name: "Mighty Fighter".age: 19.attack_type: "melee"});Copy the code

    The captureException and captureMessage implementations look something like this

    // Manually trigger an error throw, reset the error message to the one passed in by the user, and call callOnHub to trigger a report
    function captureException(exception, captureContext) {
         var syntheticException;
         try {
             throw new Error('Sentry syntheticException');
         }
         catch (exception) {
             syntheticException = exception;
         }
         return callOnHub('captureException', exception, {
             captureContext: captureContext,
             originalException: exception,
             syntheticException: syntheticException,
         });
     }
    
     function captureMessage(message, captureContext) {
         var syntheticException;
         try {
             throw new Error(message);
         }
         catch (exception) {
             syntheticException = exception;
         }
         return callOnHub('captureMessage', message, level, tslib_1.__assign({ originalException: message, syntheticException: syntheticException }, context));
     }
    Copy the code

    2. Upload the Source Map

    Sentry can upload a Source map in three different ways

    • Official CLI (Sentry-CLI)
      • The official documentation
    • Call the HTTP API
      • API
    • Webpack plug-in

    Below I mainly explain the implementation of Webpack, other ways please see. Source map uploads using @sentry/webpack-plugin.

     // An example
     yarn add --dev @sentry/webpack-plugin
    
     const SentryWebpackPlugin = require("@sentry/webpack-plugin");
     module.exports = {
       // other configuration
       configureWebpack: {
         plugins: [
           new SentryWebpackPlugin({
             // sentry-cli configuration
             authToken: process.env.SENTRY_AUTH_TOKEN,
             org: "example-org".project: "example-project".release: process.env.SENTRY_RELEASE,
             // webpack specific configuration
             include: ".".ignore: ["node_modules"."webpack.config.js"],}),],},};Copy the code

    2.1 @sentry/webpack-plugin Upload principle

    The afterEmit hook in WebPack is basically the process of retrieving the packaged file. Then filter the file type is / \. Js $| \. Map $/ end is uploaded to the sentry servers. Then delete only the files ending in /\.map$/ when deleting.

     // upload sourcemaps
     apply(compiler) {
       AfterEmit is executed after generating the file to the output directory
       compiler.hooks.afterEmit.tapAsync(this.name, async (compilation, callback) => {
         const files = this.getFiles(compilation);
         try {
           await this.createRelease();
           await this.uploadFiles(files);
           console.info('\n\u001b[32mUpload successfully.\u001b[39m\n');
         } catch (error) {
           // todo
         }
         callback(null);
       });
     }
    
     // Get the file to be uploaded
     getFiles(compilation) {
       // Retrieve the files we need from compilation. Assets
           // compilation.assets {
           // 'bundle.js': SizeOnlySource { _size: 212 },
           // 'bundle.js.map': SizeOnlySource { _size: 162 }
           // }
       return Object.keys(compilation.assets)
         .map((name) = > {
         if (this.isIncludeOrExclude(name)) {
           return { name, filePath: this.getAssetPath(compilation, name) };
         }
         return null;
       })
         .filter(Boolean);
     }
    
     // Get the absolute path to the file
     getAssetPath(compilation, name) {
        return path.join(compilation.getPath(compilation.compiler.outputPath), name.split('? ') [0]);
     }
     // Upload the file
     async uploadFile({ filePath, name }) {
       console.log(filePath);
       try {
         await request({
           url: `The ${this.sentryReleaseUrl()}/The ${this.release}/files/`.// Upload sentry path
           method: 'POST'.auth: {
             bearer: this.apiKey,
           },
           headers: {},
           formData: {
             file: fs.createReadStream(filePath),
             name: this.filenameTransform(name),
           },
         });
       } catch (e) {
         console.error(`uploadFile failed ${filePath}`); }}/ / remove sourcemaps
     sentryDel(compiler) {
       compiler.hooks.done.tapAsync(this.name, async (stats, callback) => {
         console.log('Whether to delete SourceMaps:'.this.isDeleteSourceMaps);
         if (this.isDeleteSourceMaps) {
           await this.deleteFiles(stats);
           console.info('\n\u001b[32mDelete SourceMaps done.\u001b[39m\n');
         }
         callback(null);
       });
     }
     // Delete files
     async deleteFiles(stats) {
       console.log();
       console.info('\u001b[33mStarting delete SourceMaps...\u001b[39m\n');
    
       Object.keys(stats.compilation.assets)
         .filter((name) = > this.deleteRegex.test(name))
         .forEach((name) = > {
         const filePath = this.getAssetPath(stats.compilation, name);
         if (filePath) {
           console.log(filePath);
           fs.unlinkSync(filePath);
         } else {
           console.warn('bss-plugin-sentry: cannot be deleted'${name}', the file does not exist, it may not have been created 'due to a build error); }}); }Copy the code

    @sentry/webpack-plugin does not support deleting the source map after uploading, so we can use webpack-sentry-plugin to delete the source map after uploading. You can refer to the documentation for use.

    export const SentryPluginConfig = {
        deleteAfterCompile: true.// Do you want to delete the source-map file in the current directory after uploading the source-map file
        // Sentry options are required
        organization: ' './ / group name
        project: ' '.// Name of the current project
        baseSentryURL: 'https://xxx'.// The default is https://sentry.io/api/0, that is, to upload to the sentry website. If you build your own Sentry system, you can replace sentry. IO with your own sentry system domain name.
        apiKey: ' '.// Release version name/hash is required
        release: SentryRelease, / / version
    };
    Copy the code

Principle 3.

3.1 Exception Catching

Front-end catch exception is divided into global catch and single point catch.

  • Global capture code set, easy to manage;
  • As a supplement, single point capture captures some special cases, but it is scattered and not conducive to management.
  1. Global capture
    • Capture code is centralized in one place with a global interface. The interfaces available are:
      • Window. The addEventListener (" error ")/window. AddEventListener (" unhandledrejection ")/document. AddEventListener (" click ")
    • Global listening at the framework level
      • For example,aixosThe use ofinterceptorTo intercept,
      • Vue, the reactEach has its own error collection interface
    • The global function is wrapped to automatically catch exceptions when the function is called
    • Rewrite the Patch of the instance method, wrap a layer on the basis of the original function,
      • For example, tosetTimeoutCan be overridden in the case of using the same method can also catch exceptions
  2. Global capture
    • Wrap a single block of code in business code or dot a logical flow for targeted exception catching:
    • The try... catch
    • Write a special function to collect exception information and call it when an exception occurs
    • Write a special function to wrap around other functions, resulting in a new function that behaves exactly like the original function, except that it can catch exceptions when they occur

3.2 Detailed explanation

  • window.onerror
    • Monitors JavaScript runtime errors (including syntax errors) and resource loading errors
window.onerror = function(message, source, lineno, colno, error) {... }window.addEventListener('error'.function(event) {... },true)
// Function argument:
    // message: error message (string). Can be used for events in the HTML onError ="" handler.
    // source: error script URL (string)
    // lineno: error line number
    // colno: error column number (number)
    // error: error object (objectYou can see there's a JS error monitorwindowOnEerror, used againwindowAddEventLisenter ('error'), in fact, the two can not replace each other.window.onError is a standard error capture interface, it can get the corresponding JS error;windowAddEventLisenter ('error') can also catch errors, but the JS error stack it gets is often incomplete. At the same timewindow.onError cannot get information about a resource load failure and must be usedwindowAddEventLisenter ('error') to catch resource load failures.Copy the code
  • Promise
    • PromiseThe main thing is thatunhandledrejectionThe event, that is, not beingcatchtherejectThe state of thepromise
window.addEventListener("unhandledrejection".event= > {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
Copy the code
  • SetTimeout, setInterval, requestAnimationFrameEtc.
    • It’s basically intercepting the original method by proxy and doing your own thing before calling the real method
const prevSetTimeout = window.setTimeout;

window.setTimeout = function(callback, timeout) {
  const self = this;
  return prevSetTimeout(function() {
    try {
      callback.call(this);
    } catch (e) {
      // A detailed error was caught, where the logic for log reporting is handled
      // ...
      throw e;
    }
  }, timeout);
}
Copy the code
  • VuetheVue.config.errorHandler
    • Same thing as before
// Handle Vue errorHandler in sentry
function vuePlugin(Raven, Vue) {
  var _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    / / report
    Raven.captureException(error, {
      extra: metaData
    });

    if (typeof _oldOnError === 'function') {
      // Why do you do that?
      _oldOnError.call(this, error, vm, info); }}; }module.exports = vuePlugin;
Copy the code
  • ReacttheErrorBoundary
    • ErrorBoundaryDefinition:If aclassComponentstatic getDerivedStateFromError() orcomponentDidCatch()When either (or both) of these lifecycle methods, then it becomes an error boundary. When an error is thrown, use thestatic getDerivedStateFromError()Render the standby UI, usecomponentDidCatch()Print error message
// An example of ErrorBoundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
    // This is where exceptions can be reported
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
}

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
Copy the code
  • thenSentryHow does that happen?
// The type of the ts declaration, you can see the approximate method implemented by Sentry
/** * A ErrorBoundary component that logs errors to Sentry. * Requires React >= 16 */
declare class ErrorBoundary extends React.Component<ErrorBoundaryProps.ErrorBoundaryState> {
    state: ErrorBoundaryState;
    componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void;
    componentDidMount(): void;
    componentWillUnmount(): void;
    resetErrorBoundary: () = > void;
    render(): React.ReactNode;
}

// The actual reporting place
ErrorBoundary.prototype.componentDidCatch = function (error, _a) {
  var _this = this;
  var componentStack = _a.componentStack;
  // Get the props for the configuration
  var _b = this.props, beforeCapture = _b.beforeCapture, onError = _b.onError, showDialog = _b.showDialog, dialogOptions = _b.dialogOptions;
  withScope(function (scope) {
    // Do some processing before reporting, which is equivalent to axios' request interceptor
    if (beforeCapture) {
      beforeCapture(scope, error, componentStack);
    }
    / / report
    var eventId = captureException(error, { contexts: { react: { componentStack: componentStack } } });
    // Developer callback
    if (onError) {
      onError(error, componentStack, eventId);
    }
    // Whether to display sentry's error feedback component (which is also a way to collect errors)
    if (showDialog) {
      showReportDialog(__assign(__assign({}, dialogOptions), { eventId: eventId }));
    }
    // componentDidCatch is used over getDerivedStateFromError
    // so that componentStack is accessible through state.
    _this.setState({ error: error, componentStack: componentStack, eventId: eventId });
  });
};
Copy the code
  • request
    • XHR overwrites (intercepts) send and open
    • Fetch intercepts the entire method (a case to discuss, reject)
    • Axios passes the request/response interceptor

    Note: Sentry supports automatic and manual error collection methods, but does not catch errors in asynchronous operations or interface requests, such as 404, 500, etc. In this case, we can actively report errors through sentry.caputureException ().

    1. xhrThe implementation of the
    function fill(source, name, replacementFactory) {
     var original = source[name];
     var wrapped = replacementFactory(original);
     source[name] = wrapped;
    }
    // xhr
    function instrumentXHR() :void {
       // Save the real XHR prototype
       const xhrproto = XMLHttpRequest.prototype;
       // Intercept the open method
       fill(xhrproto, 'open'.function (originalOpen: () => void) : () = >void {
           return function (this: SentryWrappedXMLHttpRequest, ... args: any[]) :void {
             const xhr = this;
             const onreadystatechangeHandler = function () :void {
               if (xhr.readyState === 4) {
                 if (xhr.__sentry_xhr__) {
                     xhr.__sentry_xhr__.status_code = xhr.status;
                   }
                 // // to Sentry
                 triggerHandlers('xhr', {
                   args,
                   endTimestamp: Date.now(),
                   startTimestamp: Date.now(), xhr, }); }};if ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
           // Intercept the onreadyStatechange method
           fill(xhr, 'onreadystatechange'.function (original: WrappedFunction) :Function {
             return function (. readyStateArgs: any[]) :void {
               onreadystatechangeHandler();
               // return to the original method
               return original.apply(xhr, readyStateArgs);
             };
           });
         } else {
           xhr.addEventListener('readystatechange', onreadystatechangeHandler);
         }
         // Call the original method
         return originalOpen.apply(xhr, args);
       };
    });
         // fill is basically a wrapper around the interception. OriginalSend is the originalSend method
       fill(xhrproto, 'send'.function (originalSend: () => void) : () = >void {
           return function (this: SentryWrappedXMLHttpRequest, ... args: any[]) :void {
             / / report sentry
             triggerHandlers('xhr', {
               args,
               startTimestamp: Date.now(),
               xhr: this});// return to the original method
             return originalSend.apply(this, args);
           };
       });
    }
    Copy the code
    • Fetch
    / / rewrite the fetch
    function instrumentFetch() {
      if(! supportsNativeFetch()) {return;
      }
      fill(global$2.'fetch'.function (originalFetch) {
        return function () {
          var args = [];
          for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
          }
          var handlerData = {
            args: args,
            fetchData: {
              method: getFetchMethod(args),
              url: getFetchUrl(args),
            },
            startTimestamp: Date.now(),
          };
          triggerHandlers('fetch', __assign({}, handlerData));
          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          return originalFetch.apply(global$2, args).then(function (response) {
            triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), response: response }));
            return response;
          }, function (error) {
            triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), error: error }));
            throw error;
          });
        };
      });
    }
    Copy the code
    • console.xxx
    function instrumentConsole() {
      if(! ('console' in global$2)) {
        return;
      }
      ['debug'.'info'.'warn'.'error'.'log'.'assert'].forEach(function (level) {
        if(! (levelin global$2.console)) {
          return;
        }
        fill(global$2.console, level, function (originalConsoleLevel) {
          return function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
              args[_i] = arguments[_i];
            }
            / / report sentry
            triggerHandlers('console', { args: args, level: level });
            // this fails for some browsers. :(
            if (originalConsoleLevel) {
              Function.prototype.apply.call(originalConsoleLevel, global$2.console, args); }}; }); }); }Copy the code

4. Performance monitoring (Recommended readingWeb Vitals)

4.1 Obtaining Key Data (for Reference only)

  • First screen time: the time when the page is displayed – the time when the request is started
  • White screen time:responseEnd - navigationStart
  • Total page download time:loadEventEnd - navigationStart
  • DNS resolution time:domainLookupEnd - domainLookupStart
  • TCP connection time:connectEnd - connectStart
  • First packet request time:responseEnd - responseStart
  • Dom interpretation time:domComplete - domInteractive
  • User operation time:domContentLoadedEventEnd - navigationStart

Note: due to the window. The preformance. Timing is a at different stages, are constantly revised a parameter object, therefore, recommended in the window. The onload performance data reading and reporting.

// Collect performance information
export const getPerformance = () = > {
    if (!window.performance) return null;
    const {timing} = window.performance
    if ((getPerformance as any).__performance__) {
        return (getPerformance as any).__performance__;
    }
    const performance = {
        // The redirection takes time
        redirect: timing.redirectEnd - timing.redirectStart,
        // Blank screen time HTML Head Script execution start time
        whiteScreen: window.__STARTTIME__ - timing.navigationStart,
        // DOM rendering takes time
        dom: timing.domComplete - timing.domLoading,
        // Page loading time
        load: timing.loadEventEnd - timing.navigationStart,
        // Page uninstallation time
        unload: timing.unloadEventEnd - timing.unloadEventStart,
        // Request time
        request: timing.responseEnd - timing.requestStart,
        // The current time when the performance information was obtained
        time: new Date().getTime(),
    };
    (getPerformance as any).__performance__ = performance;
    return performance;
};
Copy the code

sentryIn fact, performance monitoring depends onweb-vitalsThis package, mainly depends onPerformanceObserver()implementation

5. Reporting method

  • throughxhrReporting (Sentry’s approach)
    • // Determine which method to use. First check whether fetch is supported. If not, use XHR.
BrowserBackend.prototype._setupTransport = function () {
        if (!this._options.dsn) {
                // We return the noop transport here in case there is no Dsn.
                // Basebackend.prototype. _setupTransport returns an empty function without setting DSN
                return _super.prototype._setupTransport.call(this);
        }
        var transportOptions = __assign({}, this._options.transportOptions, { dsn: this._options.dsn });
        if (this._options.transport) {
                return new this._options.transport(transportOptions);
        }
        // FetchTransport is returned if Fetch is supported, XHRTransport is returned otherwise,
        // The two constructors are already mentioned at the beginning.
        if (supportsFetch()) {
                return new FetchTransport(transportOptions);
        }
        return new XHRTransport(transportOptions);
};
Copy the code
  • The FETCH mode sends requests
FetchTransport.prototype.sendEvent = function (event) {
    var defaultOptions = {
            body: JSON.stringify(event),
            method: 'POST'.referrerPolicy: (supportsReferrerPolicy() ? 'origin' : ' '),};return this._buffer.add(global$2.fetch(this.url, defaultOptions).then(function (response) { return ({
            status: exports.Status.fromHttpCode(response.status),
    }); }));
};
Copy the code
  • XHR sends the request
class XHRTransport extends BaseTransport {
  sendEvent(event) {
    return this._sendRequest(eventToSentryRequest(event, this._api), event);
  }
  _sendRequest(sentryRequest, originalPayload) {
    // Do not send if the limit of quantity % is exceeded
    if (this._isRateLimited(sentryRequest.type)) {
      return Promise.reject({
        event: originalPayload,
        type: sentryRequest.type,
        reason: `Transport locked till The ${this._disabledUntil(sentryRequest.type)} due to too many requests.`.status: 429}); }return this._buffer.add(new SyncPromise((resolve, reject) = > {
      const request = new XMLHttpRequest();
      request.onreadystatechange = () = > {
        if (request.readyState === 4) {
          const headers = {
            'x-sentry-rate-limits': request.getResponseHeader('X-Sentry-Rate-Limits'),
            'retry-after': request.getResponseHeader('Retry-After'),};this._handleResponse({ requestType: sentryRequest.type, response: request, headers, resolve, reject }); }}; request.open('POST', sentryRequest.url);
      for (const header in this.options.headers) {
        if (this.options.headers.hasOwnProperty(header)) {
          request.setRequestHeader(header, this.options.headers[header]); } } request.send(sentryRequest.body); })); }}this._buffer.add(task) {
  if (!this.isReady()) {
    return SyncPromise.reject(new SentryError('Not adding Promise due to buffer limit reached.'));
  }
  if (this._buffer.indexOf(task) === -1) {
    this._buffer.push(task);
  }
  task
    .then(() = > this.remove(task))
    	.then(null.() = > this.remove(task).then(null.() = > {
      // We have to add this catch here otherwise we have an unhandledPromiseReject      
			// because it's a new Promise chain      }));return task;
}
Copy the code
  • new Image()report
var REPORT_URL = 'xxx'	// Data reporting interface
var img = new Image; // Create the img tag
img.onload = img.onerror = function(){	// Img loading is complete or SRC loading fails
    img = null;	//img is null, so onload/onerror is not looping
};
img.src = REPORT_URL + Build._format(params); // Interface address for data reporting Splice the reporting parameter as the SRC of img
/ / or
<img src="images/bg.png" onerror="javascript:this.src='images/bgError.png';">
Copy the code
  • expand

For example, for some of our requirements, we reported some data in the way of new Image(), so it is really just a random resource?

  1. Why not request another file resource(js/CSS/the vera.ttf)To report it?

    • It depends on the nature of the browser. Typically, the browser will not actually send a resource request until the object is injected into the browser DOM tree after the resource node is created. Manipulating the DOM repeatedly not only causes performance problems, but loading JS/CSS resources can also block page rendering and affect the user experience.
    • Image requests are an exception. Image dotting not only does not need to insert DOM, as long as a new Image object in JS can initiate a request, and there is no blocking problem, in the browser environment without JS can also be dotting through the IMG tag, which is other types of resource requests can not do. So, of all the options for doing things by requesting file resources, using images is the best solution.
  2. The same is the picture, reported the selection of 1×1 transparent GIF, not otherPNG/JEPG/BMPFile?

    • The reason is not really good, need to separate. First, 1×1 pixel is the smallest legal image. And, because it is through the image dot, so the picture is best transparent, so it will not affect the display effect of the page itself, the two picture transparent as long as the use of a binary mark picture is transparent color, do not store color space data, can save volume.
    • Since transparent colors are required, you can exclude JEPG directly (BMP32 format supports transparent colors). That leaves BMP, PNG, and GIF, but why GIF? Because it’s small! GIF can save 41% more traffic than BMP and 35% less traffic than PNG.

Summary: Using the IMG element to send does not affect the presentation, performance or experience of the page, but there are no side effects, and the GIF format is used because of its small size.

6. more

6.1 the recording

  • In addition to error reporting and performance, actuallysentryIt can also record screen information to help developers locate errors more quicklyThe official documentation.sentryError recording actually mainly depends onrrwebThis package implements
    • The general process is to take a snapshot of the dom from the start, and then generate a unique ID for each node.
    • When the DOM changesMutationObserverTo listen for which oneDOMSave what changes to which properties of.
    • Monitor page mouse and keyboard interaction events to record location and interaction information, and finally used to simulate the implementation of user operations.
    • And then parse it internally (I understand this step is the hardest)
    • By rendering the DOM and usingRAFPlay it like you’re watching a video.

6.2 Sentry obtaining URL (via DSN)

  • Here I putsentryThe code to get the url for the report is concatenated with concise logic so that it is easy to understand.
// Matches DSN's re
const DSN_REGEX = / ^ (? :(\w+):)\/\/(? :(\w+)(? ::(\w+))? @)([\w.-]+)(? ::(\d+))? / / / (. +);
const dsn = 'https://[email protected]/430'; / / false DSN
// Obtain the packet information of the re match
const match = DSN_REGEX.exec(dsn);
const [protocol, publicKey, pass = ' ', host, port = ' ', lastPath] = match.slice(1);
const obj = {
  'protocol': protocol,
  'publicKey': publicKey,
  'pass': pass,
  'host': host,
  'port': port,
  'lastPath': lastPath,
  'projectId': lastPath.split('/') [0],}// Prints information
console.log('obj'.JSON.stringify(obj));
/ / {
// protocol: "https",
// publicKey: "3543756743567437634345",
// pass: "",
// host: "sentry.com",
// port: "",
// lastPath: "430",
// projectId: "430",
// }

function getBaseApiEndpoint() {
  const dsn = obj;
  const protocol = dsn.protocol ? `${dsn.protocol}: ` : ' ';
  const port = dsn.port ? ` :${dsn.port}` : ' ';
  return `${protocol}//${dsn.host}${port}${dsn.path ? ` /${dsn.path}` : ' '}/api/`;
}
console.log('getBaseApiEndpoint', getBaseApiEndpoint());
// https://sentry.com/api/

// Different event types are sent to different domains
function getIngestEndpoint(target) {
  const eventType = event.type || 'event';
  const useEnvelope = eventType === 'transaction';
  target = useEnvelope ? 'envelope' : 'store';
  const base = this.getBaseApiEndpoint();
  const dsn = obj;
  return `${base}${dsn.projectId}/${target}/ `;
}
console.log('getIngestEndpoint', getIngestEndpoint());
// https://sentry.com/api/430/store/

function getStoreOrEnvelopeEndpointWithUrlEncodedAuth() {
  return `The ${this.getIngestEndpoint()}?The ${this.encodedAuth()}`;
}
// Obtain the authentication information, which can be interpreted as a certificate
function encodedAuth() {
  const dsn = obj;
  const auth = {
    // We send only the minimum set of required information. See
    // https://github.com/getsentry/sentry-javascript/issues/2572.
    sentry_key: dsn.publicKey,
    sentry_version: SENTRY_API_VERSION = 7.// Write dead 7, different versions are different
  };
  return urlEncode(auth);
}

// Format the URL
function urlEncode(object) {
  return Object.keys(object)
    .map(key= > `The ${encodeURIComponent(key)}=The ${encodeURIComponent(object[key])}`)
    .join('&');
}
// Finally get the full URL
console.log('getStoreOrEnvelopeEndpointWithUrlEncodedAuth', getStoreOrEnvelopeEndpointWithUrlEncodedAuth());
// https://sentry.com/api/430/store/?sentry_key=3543756743567437634345&sentry_version=7
Copy the code
  • I don’t quite understand the difference between store and envelopes. According to the appearance, the format of data reported is different, and store returns with response, while envelopes returns with no response
  • Specific can refer to: store, envelopes, you can tell me in the comments section

6.3 Vue2.xIn theerrorHandleThe processing ofsrc/core/util/error.js

/* @flow */
import config from '.. /config'
import { warn } from './debug'
import { inBrowser, inWeex } from './env'
import { isPromise } from 'shared/util'
import { pushTarget, popTarget } from '.. /observer/dep'

/ / initialization
function initGlobalAPI (Vue) {
    // config
    var configDef = {};
    const config = {
    	errorHandler: null.warnHandler: null}; configDef.get =function () { return config; };
    {
      configDef.set = function () {
        warn(
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // Define the initial value of the global vue.prototype. config
    Object.defineProperty(Vue, 'config', configDef);
}

export function handleError (err: Error, vm: any, info: string) {
  // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
  // See: https://github.com/vuejs/vuex/issues/1505
  pushTarget()
  try {
    if (vm) {
      let cur = vm
      while ((cur = cur.$parent)) {
        ErrorCaptured hooks are captured globally and all of its children
        const hooks = cur.$options.errorCaptured
        if (hooks) {
          for (let i = 0; i < hooks.length; i++) {
            try {
              ErrorCaptured event is called
              // The hook can return false to prevent further propagation of the error
              const capture = hooks[i].call(cur, err, vm, info) === false
              if (capture) return
            } catch (e) {
              // If this hook has an error, it will be reported
              globalHandleError(e, cur, 'errorCaptured hook')}}}}}/ / errorHandle reported
    globalHandleError(err, vm, info)
  } finally {
    popTarget()
  }
}

function globalHandleError (err, vm, info) {
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      // if the user intentionally throws the original error in the handler,
      // do not log it twice
      // Prevent double reporting
      if(e ! == err) { logError(e,null.'config.errorHandler')
      }
    }
  }
  logError(err, vm, info)
}

// Determine whether to output to the console
function logError (err, vm, info) {
  if(process.env.NODE_ENV ! = ='production') {
    warn(`Error in ${info}:"${err.toString()}"`, vm)
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console! = ='undefined') {
    console.error(err)
  } else {
    throw err
  }
}
Copy the code
  • So where exactly does this event work?
/ / initialization data SRC/core/instance/state. Js
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
}
export function getData (data: Function, vm: Component) :any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return{}}finally {
    popTarget()
  }
}

SRC /core/util/next-tick.js when executing the nextTick callback
export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  callbacks.push(() = > {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
}

/ / some statement cycle time to carry out the instructions SRC/core/vdom/modules/directives. Js
callHook(dir, 'bind', vnode, oldVnode)
callHook(dir, 'update', vnode, oldVnode)
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
function callHook (dir, hook, vnode, oldVnode, isDestroy) {
  const fn = dir.def && dir.def[hook]
  if (fn) {
    try {
      fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
    } catch (e) {
      handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)}}}/ / watch through configuration in the SRC/core/observer/watcher. Js
get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    // Only under this condition will it be reported
    // I have not seen the configuration of user in the code or API, please let me know in the comment section
    if (this.user) {
      handleError(e, vm, `getter for watcher "The ${this.expression}"`)}else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    // Vue's deep Watch is implemented in the following way
    // Listen for all elements of this object
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}
Copy the code

6.4 sentryTest whether FETCH is supported

 // Whether fetch is supported is determined by whether the fetch can be found in the window
 function supportsFetch() {
   if(! ('fetch' in getGlobalObject())) {
     return false;
   }
   try {
     // If these attributes are supported, fetch is supported
     // MDN https://developer.mozilla.org/zh-CN/docs/Web/API/Headers
     new Headers(); // The Headers interface of the Fetch API renders the response data to a request.
     new Request(' '); // The Fetch API's Request interface renders the response data to a Request
     new Response(); // The Response interface of the Fetch API renders the Response data for a request
     return true;
   }
   catch (e) {
     return false; }}// Whether it is native is the browser fetch
function isNativeFetch(func) {
  return func && /^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
}
// Whether native fetch is supported instead of proxyed or prolyFill pairs
function supportsNativeFetch() {
  if(! supportsFetch()) {return false;
  }
  const global = getGlobalObject();
  // Fast path to avoid DOM I/O
  // eslint-disable-next-line @typescript-eslint/unbound-method
  if (isNativeFetch(global.fetch)) {
    return true;
  }
  // window.fetch is implemented, but is polyfilled or already wrapped (e.g: by a chrome extension)
  // so create a "pure" iframe to see if that has native fetch
  let result = false;
  const doc = global.document;
  // eslint-disable-next-line deprecation/deprecation
  // If the current FETCH is not native, create an iframe to determine whether the fetch is supported or not
  if (doc && typeof doc.createElement === `function`) {
    try {
      const sandbox = doc.createElement('iframe');
      sandbox.hidden = true;
      doc.head.appendChild(sandbox);
      // Check if there is a fetch method on the window
      if (sandbox.contentWindow && sandbox.contentWindow.fetch) {
        // eslint-disable-next-line @typescript-eslint/unbound-method
        result = isNativeFetch(sandbox.contentWindow.fetch);
      }
      doc.head.removeChild(sandbox);
    }
    catch (err) {
      logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err); }}return result;
}
Copy the code

6.5 Routing and Component Performance

  • In fact, I did not understand the specific purpose of this piece, if you know, please let me know in the comment section
import { matchPath } from 'react-router-dom';
const routes = [{ path: '/' }, { path: '/report' }];
// https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/react-router/
Sentry.init({
  dsn: 'xxx'.integrations: [
    new Integrations.BrowserTracing({
      // Can also use reactRouterV4Instrumentation
      // The specific routing address can be reported
      routingInstrumentation: Sentry.reactRouterV5Instrumentation(HashHistory, routes, matchPath),
    }),
  ],
})

// https://docs.sentry.io/platforms/javascript/guides/react/components/profiler/
// The withProfiler higher-order component is used to detect App components
class App extends React.Component {
  render() {
    return (
        <NestedComponent someProp={2} />); }}export default Sentry.withProfiler(App);
Copy the code

6.6 extensions

  • Exception monitoring comparison, Sentry, RollBar, Fundebug, FrontJS

6.7 A very general process

  • According to my very general understanding

6.8 Initialization Process

import * as Sentry from '@ipalfish/sentry/react';

Sentry.init({
  dsn: SENTRY_KEY || DSN,
});

// Sentry.init
function init(options) {
    options._metadata = options._metadata || {};
    if (options._metadata.sdk === undefined) {
        options._metadata.sdk = {
            name: 'sentry.javascript.react'.packages: [{name: 'npm:@sentry/react'.version: browser_1.SDK_VERSION,
                },
            ],
            version: browser_1.SDK_VERSION,
        };
    }
    browser_1.init(options);
}

// browser_1.init
export function init(options) {
    if (options === void 0) { options = {}; }
    if (options.defaultIntegrations === undefined) {
        options.defaultIntegrations = defaultIntegrations;
    }
    if (options.release === undefined) {
        var window_1 = getGlobalObject();
        // This supports the variable that sentry-webpack-plugin injects
        if(window_1.SENTRY_RELEASE && window_1.SENTRY_RELEASE.id) { options.release = window_1.SENTRY_RELEASE.id; }}if (options.autoSessionTracking === undefined) {
        options.autoSessionTracking = true;
    }
    initAndBind(BrowserClient, options);
    if(options.autoSessionTracking) { startSessionTracking(); }}// Default integration
const defaultIntegrations = [
  new InboundFilters(),
  new FunctionToString(),
  new TryCatch(), // setTimeout, setInterval, requestAnimationFrame, xhr
  new Breadcrumbs(), // console, dom, xhr, fetch, history
  new GlobalHandlers(), / / the window. The onerror unhandledrejection
  new LinkedErrors(),
  new UserAgent(),
];

function initAndBind(clientClass, options) {
    if (options.debug === true) {
        utils_1.logger.enable();
    }
    var hub = hub_1.getCurrentHub();
    var client = new clientClass(options);
    hub.bindClient(client);
}

var BrowserClient = / * *@class * / (function (_super) {
	// BrowserClient inherits from BaseClient
	__extends(BrowserClient, _super);

	function BrowserClient(options) {
		if (options === void 0) { options = {}; }
		// Pass 'BrowserBackend', 'options' to the' BaseClient 'call.
		return _super.call(this, BrowserBackend, options) || this;
	}
	return BrowserClient;
}(BaseClient));

class BaseClient {
      constructor(backendClass, options) {
          this._backend = new backendClass(options); // Get the reporting method
          this._options = options;
          if (options.dsn) {
              this._dsn = newDsn(options.dsn); }}}var BrowserBackend = / * *@class * / (function (_super) {
    __extends(BrowserBackend, _super);
	/** * set request */
	BrowserBackend.prototype._setupTransport = function () {
		if (!this._options.dsn) {
			// Basebackend.prototype. _setupTransport returns an empty function noop without setting DSN
			return _super.prototype._setupTransport.call(this);
		}
		var transportOptions = __assign({}, this._options.transportOptions, { dsn: this._options.dsn });
		if (this._options.transport) {
			return new this._options.transport(transportOptions);
		}
		// FetchTransport is returned if Fetch is supported, XHRTransport is returned otherwise,
		if (supportsFetch()) {
			return new FetchTransport(transportOptions);
		}
		return new XHRTransport(transportOptions);
	};
}(BaseBackend));
Copy the code

7

  • Docs. Sentry. IO/product/per…
  • Docs. Sentry. IO/product/per…
  • Performance indicators