Learn the overall structure of sentry source code and build your own front-end exception monitoring SDK

Preface

This is the fourth part of learning the overall structure of source code. The word "overall architecture" seems to be a little big. Let alone the overall structure of the source code. The main thing is to learn the overall structure of the code without going into the implementation of other specific functions that are not the main line. This article studies the code after packaging and integration, not the split code in the actual warehouse.

The other three are:

1.Learn the overall architecture of jQuery source code, and build your own js class library
2.Learn the overall architecture of the source code of the anderscore, and build its own functional programming class library
3.Learn the overall architecture of lodash source code, and build your own functional programming class library)

Interested readers can click to read.

Guide reading
This paper introduces sentry error monitoring principle, sentry initialization, Ajax reporting, window.onerror, window.onunhandledrection to learn sentry source code.

Develop wechat applets, and try to build an error monitoring scheme for applets. Recently, I used the Sentry applet SDK of dingxiangyuan open source. sentry-miniapp.
By the way Sentry JavaScript repository The source code of the overall architecture, so there is this article.

This paper analyzes the uncompressed source code after packaging. The total source line is more than 5000 lines, and the link address is: https://browser.sentry-cdn.com/5.7.1/bundle.js , version is v5.7.1.

The source code of this article is in my github blog. github blog sentry , you can click to view it if you want. If you think it's good, you can stop by star.

Before looking at the source code, first comb the front-end error monitoring knowledge.

Front end error monitoring knowledge

Extract from MOOC online video course: necessary skills for job hopping interview at the front end
Notes made by others: necessary skills for front-end job hopping interview-4-4 error monitoring

Classification of front end errors

1. Instant operation error: code error

try...catch

window.onerror (It can also be used.DOM2event listeners)

2.Resource load error

object.onerror: domObjectonerrorEvent

performance.getEntries()

ErrorEvent capture

3.Useperformance.getEntries()Get page image load error

var allImgs = document.getElementsByTagName('image')

var loadedImgs = performance.getEntries().filter(i => i.initiatorType === 'img')

Finally, allIms and loadedImgs can be compared to find out that the image resources are not loaded.

Error event capture code example

window.addEventListener('error', function(e) {
  console.log('capture', e)
}, true) // The event can only be triggered by capture, but cannot be triggered by bubbling.

Basic principles of reporting errors

1. Report by Ajax communication

2. Using Image object to report (mainstream mode)

Image reporting error mode:
(New image()). SRC ='https://lxchuan12.cn/error? Name = Ruochuan '

Basic principle of Sentry front end anomaly monitoring

1. Rewrite the window.onerror method and the window.onunhandledrection method

If you don't know the readers of onerror and onunhandledreection methods, you can read the relevant MDN documents. Here is a brief introduction:

MDN GlobalEventHandlers.onerror

window.onerror = function (message, source, lineno, colno, error) {
    console.log('message, source, lineno, colno, error', message, source, lineno, colno, error);
}

Parameters: < br / >
Message: error message (string). Available for event s in the HTML onerror = handler. <br/>
source: script URL (string) with error < br / >
lineno: line number (number) with error < br / >
colno: the wrong column number (number) < br / >
Error: error object < br / >

MDN unhandledrejection

When the project is rejected and there is no reject processor, the unhandledrejection event will be triggered; this may happen under the window, but it may also happen in the Worker. This is useful for debugging fallback error handling.

Sentry Source code can be searched global.onerror Positioning to specific location

 GlobalHandlers.prototype._installGlobalOnErrorHandler = function () {
    // Code is deleted
    // This. Global is window in browser.
    this._oldOnErrorHandler = this._global.onerror;
    this._global.onerror = function (msg, url, line, column, error) {}
    // code ...
 }

Similarly, you can search global.onunhandledreection to locate it.

GlobalHandlers.prototype._installGlobalOnUnhandledRejectionHandler = function () {
    // Code is deleted
    this._oldOnUnhandledRejectionHandler = this._global.onunhandledrejection;
    this._global.onunhandledrejection = function (e) {}
}

2. Upload with Ajax

Support fetch to use fetch, otherwise use XHR.

BrowserBackend.prototype._setupTransport = function () {
    // Code is deleted
    if (supportsFetch()) {
        return new FetchTransport(transportOptions);
    }
    return new XHRTransport(transportOptions);
};

2.1 fetch

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),
    }); }));
};

2.2 XMLHttpRequest

XHRTransport.prototype.sendEvent = function (event) {
    var _this = this;
    return this._buffer.add(new SyncPromise(function (resolve, reject) {
        // Familiar XMLHttpRequest
        var request = new XMLHttpRequest();
        request.onreadystatechange = function () {
            if (request.readyState !== 4) {
                return;
            }
            if (request.status === 200) {
                resolve({
                    status: exports.Status.fromHttpCode(request.status),
                });
            }
            reject(request);
        };
        request.open('POST', _this.url);
        request.send(JSON.stringify(event));
    }));
}

Next, we mainly learn the source code through three main lines: Sentry initialization, how to report Ajax, window.onerror and window.onunhandledrection.

If you see it here, you don't want to pay attention to the source code details at the end of the article, just look at the two figures in the summary 1 and 2. Or you can like or collect this article. I want to read it later.

Sentry source entry and exit

var Sentry = (function(exports){
    // code ...

    var SDK_NAME = 'sentry.javascript.browser';
    var SDK_VERSION = '5.7.1';

    // code ...
    // Several methods and properties of exported Sentry are omitted
    // Only the following are listed
    exports.SDK_NAME = SDK_NAME;
    exports.SDK_VERSION = SDK_VERSION;
    // Focus on captureMessage
    exports.captureMessage = captureMessage;
    // Focus on init
    exports.init = init;

    return exports;
}({}));

Init function initialized by Sentry.init

Initialization

// The dsn here is generated by sentry.io website.
Sentry.init({ dsn: 'xxx' });
// options are {dsn: '...'}
function init(options) {
    // If options is undefined, the assignment is empty
    if (options === void 0) { options = {}; }
    // If no defaultIntegrations are passed, the default
    if (options.defaultIntegrations === undefined) {
        options.defaultIntegrations = defaultIntegrations;
    }
    // Initialization statement
    if (options.release === undefined) {
        var window_1 = getGlobalObject();
        // This is provided for the sentry webback plugin plug-in, the variables injected by the webback plugin. This plug-in is not used here, so we will not go into it.
        // 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;
        }
    }
    // Initialize and bind
    initAndBind(BrowserClient, options);
}

getGlobalObject, inNodeEnv function

This function getGlobalObject is used in many places. In fact, what we do is relatively simple, which is to obtain global objects. In the browser is window.

/**
 * Judge whether it is a node environment
 * Checks whether we're in the Node.js or Browser environment
 *
 * @returns Answer to given question
 */
function isNodeEnv() {
    // tslint:disable:strict-type-predicates
    return Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
}
var fallbackGlobalObject = {};
/**
 * Safely get global scope object
 *
 * @returns Global scope object
 */
function getGlobalObject() {
    return (isNodeEnv()
    // The node environment is assigned to global
        ? global
        : typeof window !== 'undefined'
            ? window
            // No window self, no undefined, Web Worker environment
            : typeof self !== 'undefined'
                ? self
                // None, assign to an empty object.
                : fallbackGlobalObject);

Continue with initAndBind function

new BrowserClient(options) of initAndBind function

function initAndBind(clientClass, options) {
    // debug mode is not enabled here. logger.enable() will not execute.
    if (options.debug === true) {
        logger.enable();
    }
    getCurrentHub().bindClient(new clientClass(options));
}

You can see that initAndBind(), the first parameter is the BrowserClient constructor, and the second parameter is the options after initialization.
Next, look at the constructor BrowserClient.
The other line getCurrentHub().bindClient() doesn't look first.

BrowserClient constructor

var BrowserClient = /** @class */ (function (_super) {
    // `BrowserClient 'inherited from' BaseClient '`
    __extends(BrowserClient, _super);
    /**
     * Creates a new Browser SDK instance.
     *
     * @param options Configuration options for this SDK.
     */
    function BrowserClient(options) {
        if (options === void 0) { options = {}; }
        // Pass' BrowserBackend 'and' options' to the 'BaseClient' call.
        return _super.call(this, BrowserBackend, options) || this;
    }
    return BrowserClient;
}(BaseClient));

You can see from the code:
BrowserClient inherits from BaseClient, and passes BrowserBackend and options to BaseClient call.

First look at BrowserBackend, the BaseClient here, not for the moment.

Before looking at BrowserBackend, let's talk about inheritance, inheritance of static properties and methods.

__Extensions, extends tatistics package code implementation inheritance

The unpackaged source code is implemented using ES6 extensions. This is a packaged extension of ES6.

If you are not familiar with inheritance, you can refer to my previous articles. Interviewer: Inheritance of JS

//Inherit static methods and properties
var extendStatics = function(d, b) {
    //If the function Object.setPrototypeOf is supported, use the
    //If not, use the proto type property.
    //It's not supported yet (but it's possible that it's not, after all, browser specific.)
    //Then, for in is used to traverse the properties on the prototype chain to achieve the purpose of inheritance.
    extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return extendStatics(d, b);
};

function __extends(d, b) {
    extendStatics(d, b);
    //Declare constructor and assign d to constructor
    function __() { this.constructor = d; }
    //(prototype = b.prototype, new ()) this comma form of code, the final return is the latter, that is, new ()
    //For example (type of null, 1) returns 1
    //If b === null is created with Object.create(b), it is an empty object {} without prototype chain and other information.
    //Otherwise, return with new
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}

I have to say that the packaged code is very rigorous. My article "interviewer asked: Inheritance of JS" mentioned above does not mention the situation of not supporting "proto". It seems that this article can be further rigorously revised.
Let me think of the Vue source code for the array detection agent to determine whether to support the "proto" judgment.

// Source code of Vue JS: https://github.com/vuejs/vue/blob/dev/dist/vue.js × l526-l527
// can we use __proto__?
var hasProto = '__proto__' in {};

After seeing the inheritance of the packaged code implementation, continue to see the BrowserBackend constructor

BrowserBackend constructor (browser backend)

var BrowserBackend = /** @class */ (function (_super) {
    __extends(BrowserBackend, _super);
    function BrowserBackend() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    /**
     * Setup request
     */
    BrowserBackend.prototype._setupTransport = function () {
        if (!this._options.dsn) {
            // We return the noop transport here in case there is no Dsn.
            // dsn is not set, calling basebackend.prototype. \
            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);
        }
        // If Fetch is supported, FetchTransport instance will be returned, otherwise XHRTransport instance will be returned.
        // The specific codes of these two constructors are mentioned at the beginning.
        if (supportsFetch()) {
            return new FetchTransport(transportOptions);
        }
        return new XHRTransport(transportOptions);
    };
    // code ...
    return BrowserBackend;
}(BaseBackend));

BrowserBackend inherits from BaseBackend.

BaseBackend constructor (base backend)

/**
 * This is the base implemention of a Backend.
 * @hidden
 */
var BaseBackend = /** @class */ (function () {
    /** Creates a new backend instance. */
    function BaseBackend(options) {
        this._options = options;
        if (!this._options.dsn) {
            logger.warn('No DSN provided, backend will not do anything.');
        }
        // Call set request function
        this._transport = this._setupTransport();
    }
    /**
     * Sets up the transport so it can be used later to send requests.
     * Set send request empty function
     */
    BaseBackend.prototype._setupTransport = function () {
        return new NoopTransport();
    };
    // code ...
    BaseBackend.prototype.sendEvent = function (event) {
        this._transport.sendEvent(event).then(null, function (reason) {
            logger.error("Error while sending event: " + reason);
        });
    };
    BaseBackend.prototype.getTransport = function () {
        return this._transport;
    };
    return BaseBackend;
}());

After a series of inheritance, look back at the BaseClient constructor.

BaseClient constructor (base client)

var BaseClient = /** @class */ (function () {
    /**
     * Initializes this client instance.
     *
     * @param backendClass A constructor function to create the backend.
     * @param options Options for the client.
     */
    function BaseClient(backendClass, options) {
        /** Array of used integrations. */
        this._integrations = {};
        /** Is the client still processing a call? */
        this._processing = false;
        this._backend = new backendClass(options);
        this._options = options;
        if (options.dsn) {
            this._dsn = new Dsn(options.dsn);
        }
        if (this._isEnabled()) {
            this._integrations = setupIntegrations(this._options);
        }
    }
    // code ...
    return BaseClient;
}());

Summary 1. The new browerclient undergoes a series of inheritance and initialization

You can output the following specific results after new clientClass(options):

function initAndBind(clientClass, options) {
    if (options.debug === true) {
        logger.enable();
    }
    var client = new clientClass(options);
    console.log('new clientClass(options)', client);
    getCurrentHub().bindClient(client);
    // Original code
    // getCurrentHub().bindClient(new clientClass(options));
}

The final output is such data. I drew a picture. The focus of the prototype chain is colored, and the rest shrinks.

getCurrentHub().bindClient() of initAndBind function

Keep looking at the other line of initAndBind.

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

Get the current control center Hub, and bind the instance object of new BrowserClient() to the Hub.

getCurrentHub function

// Get current Hub control center
function getCurrentHub() {
    // Get main carrier (global for every environment)
    var registry = getMainCarrier();
    // If there is no control center on the carrier, or if its version is an old one, set a new one.
    // If there's no hub, or its an old API, assign a new one
    if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
        setHubOnCarrier(registry, new Hub());
    }
    // node to execute
    // Prefer domains over global if they are there (applicable only to Node environment)
    if (isNodeEnv()) {
        return getHubFromActiveDomain(registry);
    }
    // Return to the current control center from the carrier.
    // Return hub that lives on a global object
    return getHubFromCarrier(registry);
}

Derived functions getMainCarrier, getHubFromCarrier

<! -- get the main carrier -- >

function getMainCarrier() {
    // Carrier here is window
    // Through a series of initialization of new BrowerClient().
    // There are three attributes, globaleventprocessors, hub and logger, attached to carrier.
    var carrier = getGlobalObject();
    carrier.__SENTRY__ = carrier.__SENTRY__ || {
        hub: undefined,
    };
    return carrier;
}
// Get control center hub from the carrier
function getHubFromCarrier(carrier) {
    // Return if there is already one, and new Hub if not
    if (carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub) {
        return carrier.__SENTRY__.hub;
    }
    carrier.__SENTRY__ = carrier.__SENTRY__ || {};
    carrier.__SENTRY__.hub = new Hub();
    return carrier.__SENTRY__.hub;
}

bindClient bind client on current control center

Hub.prototype.bindClient = function (client) {
    // Get the last
    var top = this.getStackTop();
    // Bind the new BrowerClient() instance to top
    top.client = client;
};
Hub.prototype.getStackTop = function () {
    // Get the last
    return this._stack[this._stack.length - 1];
};

Summary 2. After a series of inheritance and initialization

Look back at the initAndBind function.

function initAndBind(clientClass, options) {
    if (options.debug === true) {
        logger.enable();
    }
    var client = new clientClass(options);
    console.log(client, options, 'client, options');
    var currentHub = getCurrentHub();
    currentHub.bindClient(client);
    console.log('currentHub', currentHub);
    // source code
    // getCurrentHub().bindClient(new clientClass(options));
}

Finally, you will get such a Hub instance object. The author drew a picture to show, easy to view and understand.

After initialization, let's look at the specific example.
The realization of captureMessage function.

Sentry.captureMessage('Hello, If Chuan!');

captureMessage function

After reading the code before, we know that the Fetch interface will be called eventually, so we can debug the breakpoint directly and get the following call stack.
Next, the main process of calling stack is described.

Main process of call stack:

captureMessage

function captureMessage(message, level) {
    var syntheticException;
    try {
        throw new Error(message);
    }
    catch (exception) {
        syntheticException = exception;
    }
    // Call callOnHub method
    return callOnHub('captureMessage', message, level, {
        originalException: message,
        syntheticException: syntheticException,
    });
}

=> callOnHub

/**
 * This calls a function on the current hub.
 * @param method function to call on hub.
 * @param args to pass to function.
 */
function callOnHub(method) {
    // The method here sends in 'captureMessage'
    // Put other parameters except method into args array
    var args = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        args[_i - 1] = arguments[_i];
    }
    // Get the current control center hub
    var hub = getCurrentHub();
    // This method is used to expand the args array and pass it to the hub[method] for execution.
    if (hub && hub[method]) {
        // tslint:disable-next-line:no-unsafe-any
        return hub[method].apply(hub, __spread(args));
    }
    throw new Error("No hub defined or " + method + " was not found on the hub, please open a bug report.");
}

=> Hub.prototype.captureMessage

Next, look at the captureMessage method defined on Hub.prototype.

Hub.prototype.captureMessage = function (message, level, hint) {
    var eventId = (this._lastEventId = uuid4());
    var finalHint = hint;
    // Code is deleted
    this._invokeClient('captureMessage', message, level, __assign({}, finalHint, { event_id: eventId }));
    return eventId;
};

=> Hub.prototype._invokeClient

/**
 * Internal helper function to call a method on the top client if it exists.
 *
 * @param method The method to call on the client.
 * @param args Arguments to pass to the client function.
 */
Hub.prototype._invokeClient = function (method) {
    // Also: 'captureMessage' is passed in the method here
    // Put other parameters except method into args array
    var _a;
    var args = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        args[_i - 1] = arguments[_i];
    }
    var top = this.getStackTop();
    // Get the hub of the control center, and call the captureMessage method inherited from BaseClient in the client, that is, new BrowerClient().
    // This method is used to expand the args array and pass it to the hub[method] for execution.
    if (top && top.client && top.client[method]) {
        (_a = top.client)[method].apply(_a, __spread(args, [top.scope]));
    }
};

=> BaseClient.prototype.captureMessage

BaseClient.prototype.captureMessage = function (message, level, hint, scope) {
    var _this = this;
    var eventId = hint && hint.event_id;
    this._processing = true;
    var promisedEvent = isPrimitive(message)
        ? this._getBackend().eventFromMessage("" + message, level, hint)
        : this._getBackend().eventFromException(message, hint);
        // Code is deleted
    promisedEvent
        .then(function (event) { return _this._processEvent(event, hint, scope); })
    // Code is deleted
    return eventId;
};

In the end, we call "processEvent".

=> BaseClient.prototype._processEvent

This function will eventually call

_this._getBackend().sendEvent(finalEvent);

that is

=> BaseBackend.prototype.sendEvent

BaseBackend.prototype.sendEvent = function (event) {
    this._transport.sendEvent(event).then(null, function (reason) {
        logger.error("Error while sending event: " + reason);
    });
};

=>Fetchtransport.prototype.sendevent finally sent the request

FetchTransport.prototype.sendEvent

FetchTransport.prototype.sendEvent = function (event) {
    var defaultOptions = {
        body: JSON.stringify(event),
        method: 'POST',
        // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
        // https://caniuse.com/#feat=referrer-policy
        // It doesn't. And it throw exception instead of ignoring this parameter...
        // REF: https://github.com/getsentry/raven-js/issues/1233
        referrerPolicy: (supportsReferrerPolicy() ? 'origin' : ''),
    };
    // global .fetch(this.url, defaultOptions) uses fetch to send requests
    return this._buffer.add(global$2.fetch(this.url, defaultOptions).then(function (response) { return ({
        status: exports.Status.fromHttpCode(response.status),
    }); }));
};

After watching the Ajax reporting main line, let's look at another main line of this article, window.onerror capture.

window.onerror and window.onunhandledrection capture errors

Example: call an undeclared variable.

func();

Promise does not catch errors

new Promise(() => {
    fun();
})
.then(res => {
    console.log('then');
})

captureEvent

Main process of call stack:

window.onerror

GlobalHandlers.prototype._installGlobalOnErrorHandler = function () {
    if (this._onErrorHandlerInstalled) {
        return;
    }
    var self = this; // tslint:disable-line:no-this-assignment
    // In the browser, this. Global. Is window.
    this._oldOnErrorHandler = this._global.onerror;
    this._global.onerror = function (msg, url, line, column, error) {
        var currentHub = getCurrentHub();
        // Code is deleted
        currentHub.captureEvent(event, {
            originalException: error,
        });
        if (self._oldOnErrorHandler) {
            return self._oldOnErrorHandler.apply(this, arguments);
        }
        return false;
    };
    this._onErrorHandlerInstalled = true;
};

window.onunhandledrejection

GlobalHandlers.prototype._installGlobalOnUnhandledRejectionHandler = function () {
    if (this._onUnhandledRejectionHandlerInstalled) {
        return;
    }
    var self = this; // tslint:disable-line:no-this-assignment
    this._oldOnUnhandledRejectionHandler = this._global.onunhandledrejection;
    this._global.onunhandledrejection = function (e) {
        // Code is deleted
        var currentHub = getCurrentHub();
        currentHub.captureEvent(event, {
            originalException: error,
        });
        if (self._oldOnUnhandledRejectionHandler) {
            return self._oldOnUnhandledRejectionHandler.apply(this, arguments);
        }
        return false;
    };
    this._onUnhandledRejectionHandlerInstalled = true;
};

Common: currentHub.captureEvent will be called

currentHub.captureEvent(event, {
    originalException: error,
});

=> Hub.prototype.captureEvent

In the end, call ﹐ invokeClient. The calling process is similar to captureMessage. I will not repeat it here.

this._invokeClient('captureEvent')

=> Hub.prototype._invokeClient

=> BaseClient.prototype.captureEvent

=> BaseClient.prototype._processEvent

=> BaseBackend.prototype.sendEvent

=> FetchTransport.prototype.sendEvent

Finally, the function is also called to send the request.

It can be said that there are different ways to achieve the same goal. The writing has been basically finished so far. Finally, let's summarize.

summary

Sentry javascript source code makes efficient use of JS prototype chain mechanism. It's amazing and worth learning.

This paper introduces sentry error monitoring principle, sentry initialization, Ajax reporting, window.onerror, window.onunhandledrection to learn sentry source code. There are many details and constructors that are not analyzed.

There are 25 constructors (classes) in total, and 9 are mentioned, including Hub, BaseClient, BaseBackend, BaseTransport, FetchTransport, XHRTransport, BrowserBackend, BrowserClient, and GlobalHandlers.

Others not mentioned are sentryrerror, Logger, Memo, SyncPromise, PromiseBuffer, Span, Scope, Dsn, API, NoopTransport, FunctionToString, InboundFilters, TryCatch, Breadcrumbs, LinkedErrors, UserAgent.

There are many other constructors (classes) worth learning, such as synchronous Promise (SyncPromise).
Interested readers can see the source code written in typescript in this official warehouse. SyncPromise You can also see the uncompressed code after packaging.

It takes time to read the source code, and it takes more time to write down the article (for example, writing this article spans more than ten days...), but the gains are generally large.

If readers find something wrong or can be improved, or where they don't write clearly, please comment. In addition, I think it's well written and helpful for you. You can like it, comment, forward and share it. It's also a kind of support for the author. Thank you very much.

Recommended reading

Zhihu didi cloud: super detailed! Build a front-end error monitoring system
Nuggets BlackHole1: JavaScript integration Sentry
Dingxiangyuan open source Sentry applet SDK sentry-miniapp
sentry official website
Sentry JavaScript repository

Previous articles of the author

Learn the overall architecture of lodash source code, and build your own functional programming class library
Learn the overall architecture of the source code of the anderscore, and build its own functional programming class library
Learn the overall architecture of jQuery source code, and build your own js class library
Interviewer: Inheritance of JS
Interviewer: JS's this point
Interviewer asked: can we simulate the call and apply methods of JS?
Interviewer: can you simulate the bind method of JS?
Interviewer: can we simulate the new operator of JS?
The front end uses the puppeter crawler to generate the PDF of React.js small book and merge it

about

Author: he often wanders in the Jianghu under the name of Ruochuan. On the front road, PPT enthusiasts know little, only good at learning.
Personal blog - ruokawa - link address of this article , reconstructed with vuepress, the reading experience may be better
Nuggets column , welcome to~
Segment fault front view column , welcome to~
Zhihu front end vision column , welcome to~
github blog , relevant source code and resources are put here to find a star^_^~

WeChat public No.

Perhaps more interesting WeChat public number, long scan code attention. You can also add wechat lxchuan12, indicate the source, and pull you into the front-end vision communication group.

Tags: Javascript github Programming SDK

Posted on Sat, 02 Nov 2019 01:09:57 -0700 by Waire