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
-
Automatically collect
- Sensorless collection
-
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
- Official CLI (Sentry-CLI)
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.
- 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,
aixos
The use ofinterceptor
To intercept, Vue, the react
Each has its own error collection interface
- For example,
- 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, to
setTimeout
Can be overridden in the case of using the same method can also catch exceptions
- For example, to
- Capture code is centralized in one place with a global interface. The interfaces available are:
- 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
Promise
The main thing is thatunhandledrejection
The event, that is, not beingcatch
thereject
The state of thepromise
window.addEventListener("unhandledrejection".event= > {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
Copy the code
SetTimeout, setInterval, requestAnimationFrame
Etc.- 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
Vue
theVue.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
React
theErrorBoundary
ErrorBoundary
Definition:If aclass
Componentstatic 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
- then
Sentry
How 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 ().
xhr
The 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
sentry
In fact, performance monitoring depends onweb-vitals
This package, mainly depends onPerformanceObserver()
implementation
5. Reporting method
- through
xhr
Reporting (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?
-
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.
-
The same is the picture, reported the selection of 1×1 transparent GIF, not other
PNG/JEPG/BMP
File?- 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, actually
sentry
It can also record screen information to help developers locate errors more quicklyThe official documentation.sentry
Error 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 changes
MutationObserver
To listen for which oneDOM
Save 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 using
RAF
Play it like you’re watching a video.
6.2 Sentry obtaining URL (via DSN)
- Here I put
sentry
The 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.x
In theerrorHandle
The 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 sentry
Test 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