One, foreword
Recently, I am very interested in front-end monitoring, so I used Sentry, an excellent open source library in front-end monitoring, and studied the Sentry source code to sort out this series of articles, hoping to help you better understand the principle of front-end monitoring.
In this series of articles, I will explain the whole process in an easy way, combining the API on the official website with the flow chart.
Here are the topics I have completed and plan for my next post:
- Understand Sentry initialization
- How Sentry handles error data (this article)
- How does Sentry report the error when he gets it?
How to debug Sentry source code you can follow the following (my version is: “5.29.2”) :
- git clone
[email protected]:getsentry/sentry-javascript.git
- Go to Packages/Browser for NPM I download dependencies
- Then NPM run Build in Packages/Browser to generate build folder
- Into the packages/browser/examples open index. The HTML can happy debugging (here suggest that use the Live Server open)
- Note: packages/browser/examples of bundle. Js source after the packaging, he pointed to the packages/browser/build/bundle. Js. You’ll also find bundle.es6.js in the build directory. If you want to read it in es6 mode, you can replace it with bundle.es6.js
Second, the introduction
Let’s take a look at the basics of front-end errors:
2.1 Common types of front-end errors:
(1) ECMAScript Execeptions
Instances of errors that occur while script code is running are thrown. In addition to the general Error constructor, JavaScript has seven other types of Error constructors
- SyntaxError: An exception raised by the use of an undefined or incorrect syntax. Syntax errors can be detected during compilation or parsing of source code.
- ReferenceError: ReferenceError represents an error that occurs when a variable that does not exist is referenced.
- RangeError: a RangeError represents when a value is not in its allowed range or set.
- TypeError indicates an error that occurs when the type of a value is not the expected type.
- URIError: URL errors are used to indicate errors caused by using the global URI handler in a wrong way.
- EvalError: An error in the eval function represents an error about the eval function. This exception is no longer thrown by JavaScript, but the EvalError object remains compatible.
- InternalError: Engine error indicates an error that occurs inside the JavaScript engine (for example, “InternalError: too much recursion”).
(2) DOMException
The latest DOM specification defines a set of error types, compatible with the old browsing DOMError interface, complete and normalize DOM error types.
- IndexSizeError: The index is not in the allowed range
- HierarchyRequestError: The node tree hierarchy is wrong.
- WrongDocumentError: Object is in the wrong Document.
- InvalidCharacterError* : String contains invalid characters.
- NoModificationAllowedError: object cannot be modified.
- NotFoundError: Object cannot be found.
- NotSupportedError: the operation is not supported
- InvalidStateError: The object is an invalid state.
- SyntaxError: The string does not match the expected pattern.
- InvalidModificationError: The object cannot be modified in this way.
- NamespaceError: Operations are not allowed in XML namespaces.
- InvalidAccessError: The object does not support this operation or argument.
- TypeMismatchError: The type of the object does not match the expected type.
- SecurityError: This operation is unsafe.
- NetworkError: a NetworkError occurs.
- AbortError: The operation is aborted.
- URLMismatchError: A given URL does not match another URL.
- QuotaExceededError: The given quota has been exceeded.
- TimeoutError: Operation times out.
- InvalidNodeTypeError: The node for this operation is incorrect or the ancestor is incorrect.
- DataCloneError: The object cannot be cloned.
2.2 Front-end error exceptions are classified according to capture methods
(1) Script error
Script errors can be classified as compile-time errors and runtime errors
Script errors can be caught by:
-
try catch
Advantages:
- By the try… Catch tells us what went wrong, and we also have stack information that tells us what line and column in which file the error occurred.
Disadvantages:
- Only exceptions to synchronized code can be caught
- Catching errors is intrusive and inappropriate as a monitoring system
-
window.onerror
Advantages:
-
Can catch errors globally
-
Return true at the end of window.onerror so that the browser does not send the error message to the console
/* * @param MSG {String} : error message * @param url{String} : error script url * @param line{Number} : error code * @param colunm{Number} : Error column * @param error{object} : error object */ window.onerror = function (msg, url, line, colunm, error) { return true; } Copy the code
Disadvantages:
- Catch only immediate runtime errors, not resource load errors (
Object. Onerror will terminate after it is captured, so window.onerror cannot catch resource loading errors
)
-
Script Cross domain scripting error capture:
For performance reasons, we usually put the script files in the CDN, which greatly speeds up the first screen time. However, if a Script Error occurs, for security reasons, the browser cannot capture detailed Error information for scripts from different sources and only displays Script Error
Solution:
- Can be found in
script
Tag, addcrossorigin
Property (Recommendedwebpack
Plug-ins add automatically). - Access-control-origin :*
(2) Resource loading errors
Resource loading errors include: img, script, link, audio, video, iframe…
Resource loading and script error similarities and differences:
Similarities:
- Monitoring resource errors is essentially the same as monitoring regular script errors, monitoring error events to achieve error capture.
Difference:
- Script error parameter object
instanceof
ErrorEvent
, while resource error parameter objectinstanceof
Event
. (due toErrorEvent inherits from Event
, so whether it’s a script error or a resource error parameter object, theyAll instanceof Event
, so, need to determine the script error). - The script error parameter object contains
message
Resource errors do not.
Resource loading capture mode:
-
Object. onError: For example, img tags and script tags can add onError events to catch resource loading errors
-
Performance. GetEntries: You can obtain the number of resources that are successfully loaded from the website through performance. GetEntries
Therefore, allIms and loadedImgs can be compared to find the image resource is not loaded items
const allImgs = document.getElementsByTagName('image') const loadedImgs = performance.getEntries().filter(i= > i.initiatorType === 'img') Copy the code
(3) The Promise is wrong
The above try catch and window. onError do not catch a Promise error (because it is asynchronous), and when a Promise is rejected and there is no Reject handler, The unhandledrejection event is raised. When a Promise is rejected and a Reject handler is present, the rejectionHandled event is raised. Description: Sentry side collection is not only reject errors that window. Unhandledrejection
2.3 Error Reporting Method
-
Reporting using Ajax communication (Sentry style)
-
The IMG request is reported, and the URL parameter contains error information
SRC = ‘docs. Sentry. IO /error? The error… ‘
Three, how to monitor errors
We can look at the picture below, this section will focus on such as explained in the Sentry initialization, how to monitor the wrong, to the window. The onerror and window unhandledrejection all did what
About Sentry initialization integration content can refer to play front-end monitoring, comprehensive analysis of Sentry source code (a) | understand Sentry initialization
Monitoring error steps are as follows:
3.1 the initialization
The user uses sentry. init to initialize, passing in parameters
3.2 Binding a Control center Hub
Init calls initAndBind to initialize the client and bind the client to the hub. The bindClient method in initAndBind binds the client to the hub control center
3.3 integrated install
Focus on the Set Integrations method in bindClient
bindClient(client) {
const top = this.getStackTop();
top.client = client;
if(client && client.setupIntegrations) { client.setupIntegrations(); }}Copy the code
setupIntegrations
setupIntegrations() {
if (this._isEnabled()) {
this._integrations = setupIntegrations(this._options); }}function setupIntegrations(options) {
const integrations = {};
getIntegrationsToSetup(options).forEach(integration= > {
integrations[integration.name] = integration;
setupIntegration(integration);
});
return integrations;
}
Copy the code
Analysis:
- SetupIntegrations iterates through integrations and installs default or custom integrations
- GetIntegrationsToSetup is just get integrations
Note: _isEnabled is an option passed in by the user. You cannot set enabled to false and DSN to null, otherwise the data will not be collected and sent
3.4 setupOnce
Then look at setupIntegration
/** Setup given integration */
function setupIntegration(integration) {
if(installedIntegrations.indexOf(integration.name) ! = = -1) {
return;
}
integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
installedIntegrations.push(integration.name);
logger.log(`Integration installed: ${integration.name}`);
}
Copy the code
Analysis:
- Here the integration is traversed, executing the setupOnce method
- There are seven by default, and we just have to focus on the New GlobalHandlers
- When a GlobalHandlers install is installed, its setupOnce method is executed
setupOnce
setupOnce() {
Error.stackTraceLimit = 50;
if (this._options.onerror) {
logger.log('Global Handler attached: onerror');
this._installGlobalOnErrorHandler();
}
if (this._options.onunhandledrejection) {
logger.log('Global Handler attached: onunhandledrejection');
this._installGlobalOnUnhandledRejectionHandler(); }}Copy the code
Analysis:
- _options is set at new GlobalHandlers()
Onerror and onunHandledrejection default to true
From here you can see that Sentry handles run errors and Promise errors differently
3.5 the window. The onerror
_installGlobalOnErrorHandler corresponding to the window. The onerror type
_installGlobalOnErrorHandler() {
if (this._onErrorHandlerInstalled) {
return;
}
addInstrumentationHandler({
callback: (data) = > {
// ...
},
type: 'error'});this._onErrorHandlerInstalled = true;
}
Copy the code
Analysis:
- _installGlobalOnErrorHandler for incoming addInstrumentationHandler method
callback
andtype
- The callback method has nothing to do with initialization, which we’ll focus on in the next chapter.
- Type Indicates the type for
Distinguish current type
In _installGlobalOnUnhandledRejectionHandler will pass into the type: ‘unhandledrejection’
3.6 the window unhandledrejection
_installGlobalOnUnhandledRejectionHandler corresponding window. Unhandledrejection
_installGlobalOnUnhandledRejectionHandler() {
if (this._onUnhandledRejectionHandlerInstalled) {
return;
}
addInstrumentationHandler({
callback: (e) = > {
// ...
},
type: 'unhandledrejection'});this._onUnhandledRejectionHandlerInstalled = true;
}
Copy the code
With _installGlobalOnErrorHandler similarly
3.7 addInstrumentationHandler
To see the general addInstrumentationHandler method
/**
* Add handler that will be called when given type of instrumentation triggers.
* Use at your own risk, this might break without changelog notice, only used internally.
* @hidden* /
const handlers = {};
function addInstrumentationHandler(handler) {
if(! handler ||typeofhandler.type ! = ='string' || typeofhandler.callback ! = ='function') {
return;
}
handlers[handler.type] = handlers[handler.type] || [];
handlers[handler.type].push(handler.callback);
instrument(handler.type);
}
Copy the code
Analysis:
- AddInstrumentationHandler the type and the callback passed to handlers,
When an error occurs, the corresponding callback method is executed
. - AddInstrumentationHandler will end
Call the instrument method based on type type
To implement various methods.
3.8 instrument
Instrument lets you know what cases Sentry handles. If you are interested in other aspects, you can view the corresponding source code, which I will also open a special topic after part of the content.
function instrument(type) {
if (instrumented[type]) {
return;
}
instrumented[type] = true;
switch (type) {
case 'console':
instrumentConsole();
break;
case 'dom':
instrumentDOM();
break;
case 'xhr':
instrumentXHR();
break;
case 'fetch':
instrumentFetch();
break;
case 'history':
instrumentHistory();
break;
case 'error':
instrumentError();
break;
case 'unhandledrejection':
instrumentUnhandledRejection();
break;
default:
logger.warn('unknown instrumentation type:', type); }}Copy the code
3.9 Key points: instrumentError
let _oldOnErrorHandler = null;
/** JSDoc */
function instrumentError() {
_oldOnErrorHandler = global$2.onerror;
global$2.onerror = function (msg, url, line, column, error) {
triggerHandlers('error', {
column,
error,
line,
msg,
url,
});
if (_oldOnErrorHandler) {
return _oldOnErrorHandler.apply(this.arguments);
}
return false;
};
}
Copy the code
Analysis:
- If you are familiar with
Aop (aspect oriented programming)
Winodw.onerror has been hijacked to add the triggerHandlers method. - And when we listen for onError, we call the triggerHandlers method based on type ‘error’
handlers
Find the corresponding types of callback methods, namely _installGlobalOnErrorHandler callback methods
Key points: 3.10 instrumentUnhandledRejection.
function instrumentUnhandledRejection() {
_oldOnUnhandledRejectionHandler = global$2.onunhandledrejection;
global$2.onunhandledrejection = function (e) {
triggerHandlers('unhandledrejection', e);
if (_oldOnUnhandledRejectionHandler) {
// eslint-disable-next-line prefer-rest-params
return _oldOnUnhandledRejectionHandler.apply(this.arguments);
}
return true;
};
}
Copy the code
With instrumentError similarly
At this point, you have completed the entire initialization step. The next step is to focus on the different outputs for each type of error.
What does Sentry do with error messages
Now let’s first take a look at _installGlobalOnErrorHandler and _installGlobalOnUnhandledRejectionHandler callback
_installGlobalOnErrorHandler:
callback: data= > {
const error = data.error;
const currentHub = getCurrentHub();
const hasIntegration = currentHub.getIntegration(GlobalHandlers);
const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
if(! hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {return;
}
const client = currentHub.getClient();
const event = isPrimitive(error)
? this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)
: this._enhanceEventWithInitialFrame(
eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,
rejection: false,
}),
data.url,
data.line,
data.column,
);
addExceptionMechanism(event, {
handled: false.type: 'onerror'}); currentHub.captureEvent(event, {originalException: error,
});
},
Copy the code
_installGlobalOnUnhandledRejectionHandler
callback: e= > {
let error = e;
// ...
const currentHub = getCurrentHub();
const hasIntegration = currentHub.getIntegration(GlobalHandlers);
const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
if(! hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {return true;
}
const client = currentHub.getClient();
const event = isPrimitive(error)
? this._eventFromRejectionWithPrimitive(error)
: eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,
rejection: true}); event.level =exports.Severity.Error;
addExceptionMechanism(event, {
handled: false.type: 'onunhandledrejection'}); currentHub.captureEvent(event, {originalException: error,
});
return;
},
Copy the code
From the source code looks _installGlobalOnErrorHandler and _installGlobalOnUnhandledRejectionHandler are very similar, we unified analysis together.
4.1 shouldIgnoreOnError
Please pay attention to this function, let’s have a look at the source:
let ignoreOnError = 0;
function shouldIgnoreOnError() {
return ignoreOnError > 0;
}
Copy the code
Analysis:
-
ShouldIgnoreOnError should return true if you throw a new Error directly
-
InstrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM instrumentDOM
-
As a result, function is initialized with a wrap method, so ignoreOnError +1 when reporting an error. This is why callback is not reported
try{... }catch{ ignoreNextOnError() } ------------------------------------------ function ignoreNextOnError() { // onerror should trigger before setTimeout ignoreOnError += 1; setTimeout(() = > { ignoreOnError -= 1; }); } Copy the code
4.2 _installGlobalOnErrorHandler
with_installGlobalOnUnhandledRejectionHandler
The difference between
Although these two functions may look a lot, they are almost the same, except for error handling:
_installGlobalOnErrorHandler
const event = isPrimitive(error)
? this._eventFromIncompleteOnError(...)
: this._enhanceEventWithInitialFrame( eventFromUnknownInput(...) )Copy the code
_installGlobalOnUnhandledRejectionHandler
const event = isPrimitive(error)
? this._eventFromRejectionWithPrimitive(...) : eventFromUnknownInput(...) ;Copy the code
Analysis:
- through
isPrimitive
It takes different actions to determine whether an error type is a primitive type or a reference type
So we divided it into four modules:
eventFromUnknownInput
_enhanceEventWithInitialFrame
._eventFromIncompleteOnError
._eventFromRejectionWithPrimitive
To examine what is done to the error:
4.3 Important: eventFromUnknownInput
eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,
rejection: false,}) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromUnknownInput(exception, syntheticException, options = {}) {
let event;
if (isErrorEvent(exception) && exception.error) {
// If it is an ErrorEvent with `error` property, extract it to get actual Error
const errorEvent = exception;
// eslint-disable-next-line no-param-reassign
exception = errorEvent.error;
event = eventFromStacktrace(computeStackTrace(exception));
return event;
}
if (isDOMError(exception) || isDOMException(exception)) {
// If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
// then we just extract the name, code, and message, as they don't provide anything else
// https://developer.mozilla.org/en-US/docs/Web/API/DOMError
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException
const domException = exception;
const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
const message = domException.message ? `${name}: ${domException.message}` : name;
event = eventFromString(message, syntheticException, options);
addExceptionTypeValue(event, message);
if ('code' in domException) {
event.tags = Object.assign(Object.assign({}, event.tags), { 'DOMException.code': `${domException.code}` });
}
return event;
}
if (isError(exception)) {
// we have a real Error object, do nothing
event = eventFromStacktrace(computeStackTrace(exception));
return event;
}
if (isPlainObject(exception) || isEvent(exception)) {
// If it is plain Object or Event, serialize it manually and extract options
// This will allow us to group events based on top-level keys
// which is much better than creating new group when any key/value change
const objectException = exception;
event = eventFromPlainObject(objectException, syntheticException, options.rejection);
addExceptionMechanism(event, {
synthetic: true});return event;
}
// If none of previous checks were valid, then it means that it's not:
// - an instance of DOMError
// - an instance of DOMException
// - an instance of Event
// - an instance of Error
// - a valid ErrorEvent (one with an error property)
// - a plain Object
//
// So bail out and capture it as a simple message:
event = eventFromString(exception, syntheticException, options);
addExceptionTypeValue(event, `${exception}`.undefined);
addExceptionMechanism(event, {
synthetic: true});return event;
}
Copy the code
Analysis:
-
Parameter Description:
- Exception is an error message
Notice that the second argument is undefind
SyntheticException passed in eventFromPlainObject and eventFromString is undefined!! No error stack is generated.- The third parameter attachStacktrace here is
The user set attachStacktrace in sentry.init
On behalf ofYou need to trace the error stack
-
IsErrorEvent if the error type isErrorEvent eventFromStacktrace(computeStackTrace(exception))
function isErrorEvent(wat) { return Object.prototype.toString.call(wat) === '[object ErrorEvent]'; } Copy the code
-
IsDOMError for DOMError (deprecated), isDOMException for DOMException, call eventFromString(note that the second argument syntheticException is null)
function isDOMError(wat) { return Object.prototype.toString.call(wat) === '[object DOMError]'; } function isDOMException(wat) { return Object.prototype.toString.call(wat) === '[object DOMException]'; } Copy the code
-
IsError, Error or Exception or DOMException will go here, will go eventFromStacktrace(computeStackTrace(Exception))
function isError(wat) { switch (Object.prototype.toString.call(wat)) { case '[object Error]': return true; case '[object Exception]': return true; case '[object DOMException]': return true; default: return isInstanceOf(wat, Error); }}Copy the code
-
IsPlainObject or isEvent for ordinary messages will take eventFromPlainObject(note that the second argument syntheticException is null)
function isEvent(wat) { return typeofEvent ! = ='undefined' && isInstanceOf(wat, Event); } function isPlainObject(wat) { return Object.prototype.toString.call(wat) === '[object Object]'; } Copy the code
-
The rest is treated as simple messages
In general, handling eventFromStacktrace, eventFromPlainObject, and eventFromString is all about getting an error message for further data processing.
ComputeStackTrace is the key to erase differences and generate error stack
(1) Key: computeStackTrace gets the error stack
ComputeStackTrace builds on some of the processing methods in TraceKit, primarily by smoothing out the error stack differences between browsers
function computeStackTrace(ex) {
let stack = null;
let popSize = 0;
// ...
try {
stack = computeStackTraceFromStackProp(ex);
if (stack) {
returnpopFrames(stack, popSize); }}catch (e) {
// no-empty
}
return {
message: extractMessage(ex),
name: ex && ex.name,
stack: [].failed: true}; }Copy the code
computeStackTraceFromStackProp
Concrete implementation of smoothing out browser differences
function computeStackTraceFromStackProp(ex) {
if(! ex || ! ex.stack) {return null;
}
const stack = [];
const lines = ex.stack.split('\n');
let isEval;
let submatch;
let parts;
let element;
for (let i = 0; i < lines.length; ++i) {
if ((parts = chrome.exec(lines[i]))) {
const isNative = parts[2] && parts[2].indexOf('native') = = =0; // start of line
isEval = parts[2] && parts[2].indexOf('eval') = = =0; // start of line
if (isEval && (submatch = chromeEval.exec(parts[2))) {// throw out eval line/column and use top-most line/column number
parts[2] = submatch[1]; // url
parts[3] = submatch[2]; // line
parts[4] = submatch[3]; // column
}
element = {
// working with the regexp above is super painful. it is quite a hack, but just stripping the `address at `
// prefix here seems like the quickest solution for now.
url: parts[2] && parts[2].indexOf('address at ') = = =0 ? parts[2].substr('address at '.length) : parts[2].func: parts[1] || UNKNOWN_FUNCTION,
args: isNative ? [parts[2]] : [],
line: parts[3]? +parts[3] : null.column: parts[4]? +parts[4] : null}; }else if ((parts = winjs.exec(lines[i]))) {
element = {
url: parts[2].func: parts[1] || UNKNOWN_FUNCTION,
args: [].line: +parts[3].column: parts[4]? +parts[4] : null}; }else if ((parts = gecko.exec(lines[i]))) {
isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
if (isEval && (submatch = geckoEval.exec(parts[3))) {// throw out eval line/column and use top-most line number
parts[1] = parts[1] | |`eval`;
parts[3] = submatch[1];
parts[4] = submatch[2];
parts[5] = ' '; // no column when eval
} else if (i === 0 && !parts[5] && ex.columnNumber ! = =void 0) {
// FireFox uses this awesome columnNumber property for its top frame
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
// so adding 1
// NOTE: this hack doesn't work if top-most frame is eval
stack[0].column = ex.columnNumber + 1;
}
element = {
url: parts[3].func: parts[1] || UNKNOWN_FUNCTION,
args: parts[2]? parts[2].split(', ') : [],
line: parts[4]? +parts[4] : null.column: parts[5]? +parts[5] : null}; }else {
continue;
}
if(! element.func && element.line) { element.func = UNKNOWN_FUNCTION; } stack.push(element); }if(! stack.length) {return null;
}
return {
message: extractMessage(ex),
name: ex.name,
stack,
};
}
Copy the code
Analysis:
-
Ex. Stack allows you to get the current error stack.
Let me give you an example of a mistake. The error stack obtained is:
Error: externalLibrary method broken: 1610359373199 at externalLibrary (https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js:2 9) : at https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js:5 : 1." Copy the code
-
Split (‘\n ‘) to an array. We need to iterate over each Error stack because, for example, ‘Error: externalLibrary method broken: 1610359373199’ is the same browser and can be skipped
[ 'Error: externalLibrary method broken: 1610359373199'.'at externalLibrary (https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js:2:9)'.'at https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-li b.js:5:1" ' ] Copy the code
-
It then uses the re to determine which browser kernel the current error belongs to, and then does different processing to smooth out the differences
-
Finally, the stack is returned with args, line, func, URL data format
args: [] column: 9 func: "externalLibrary" line: 2 url: "https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js" Copy the code
(2) eventFromStacktrace, eventFromString and eventFromPlainObject
All three of these are explained in a unified way because the principle is the same, you take the error stack and so on and process it further,
Note that both eventFromString and syntheticException passed in by eventFromPlainObject are null so there is no error stack
Take a look at their source code:
function eventFromStacktrace(stacktrace) {
const exception = exceptionFromStacktrace(stacktrace);
return {
exception: {
values: [exception], }, }; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromString(input, syntheticException, options = {}) {
const event = {
message: input,
};
if (options.attachStacktrace && syntheticException) {
const stacktrace = computeStackTrace(syntheticException);
const frames = prepareFramesForEvent(stacktrace.stack);
event.stacktrace = {
frames,
};
}
returnevent; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -function eventFromPlainObject(exception, syntheticException, rejection) {
const event = {
exception: {
values: [{type: isEvent(exception) ? exception.constructor.name : rejection ? 'UnhandledRejection' : 'Error'.value: `Non-Error ${
rejection ? 'promise rejection' : 'exception'
} captured with keys: ${extractExceptionKeysForMessage(exception)}`,}]},extra: {
__serialized__: normalizeToSize(exception),
},
};
if (syntheticException) {
const stacktrace = computeStackTrace(syntheticException);
const frames = prepareFramesForEvent(stacktrace.stack);
event.stacktrace = {
frames,
};
}
return event;
}
Copy the code
Analysis:
-
ComputeStackTrace is what we talked about above, smoothing out differences and getting the error stack
-
Exception contains error types and information
-
ExceptionFromStacktrace actually calls prepareFramesForEvent to get the error stack
-
Take a look at the data returned
exception: { stacktrace: [{colno: 1.filename: 'https://rawgit.com/kamilogorek/cfbe9f92196c6c61053e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js'.function: '? '.in_app: true.lineno: 5}, {colno: 9.filename: 'https://rawgit.com/kamilogorek/cfbe9f92196c6c610535e2fd2ad4a84205cd71e2496a445ebe1d/external-lib.js'.function: 'externalLibrary'.in_app: true.lineno: 2,},]; type:'Error'; value: 'externalLibrary method broken: 1610364003791'; } Copy the code
(3) Summary
EventFromUnknownInput is a lot of code, but very clear.
Follow one of the methods eventFromStacktrace, eventFromString, and eventFromPlainObject based on the type of error message. For example, if the error type is DOMError, DOMException or ordinary object, there is no error stack message, while others will use computeStackTrace to erase the differences between different browsers, obtain the error stack, and finally return a unified structure for error data processing.
4.4 _enhanceEventWithInitialFrame
_enhanceEventWithInitialFrame(event, url, line, column) {
event.exception = event.exception || {};
event.exception.values = event.exception.values || [];
event.exception.values[0] = event.exception.values[0) | | {}; event.exception.values[0].stacktrace = event.exception.values[0].stacktrace || {};
event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames || [];
const colno = isNaN(parseInt(column, 10))?undefined : column;
const lineno = isNaN(parseInt(line, 10))?undefined : line;
const filename = isString(url) && url.length > 0 ? url : getLocationHref();
if (event.exception.values[0].stacktrace.frames.length === 0) {
event.exception.values[0].stacktrace.frames.push({
colno,
filename,
function: '? '.in_app: true,
lineno,
});
}
returnevent; }}Copy the code
Analysis:
- Through eventFromUnknownInput get event, url, the line, call _enhanceEventWithInitialFrame after colom
- _enhanceEventWithInitialFrame ensure exception, values, stacktrace, frames set have default values
4.5 _eventFromRejectionWithPrimitive
_eventFromRejectionWithPrimitive(reason) {
return {
exception: {
values: [{type: 'UnhandledRejection'.// String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
value: `Non-Error promise rejection captured with value: The ${String(reason)}`,},],},}; }Copy the code
Analysis:
- The code simply returns the default format
4.6 _eventFromIncompleteOnError
this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)
--------------------------------------------------------------------------------------
/** * This function creates a stack from an old, error-less onerror handler. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_eventFromIncompleteOnError(msg, url, line, column) {
const ERROR_TYPES_RE = / ^ (? :[Uu]ncaught (? :exception: )?) ? (? : ((? :Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )? (.*)$/i;
// If 'message' is ErrorEvent, get real message from inside
let message = isErrorEvent(msg) ? msg.message : msg;
let name;
if (isString(message)) {
const groups = message.match(ERROR_TYPES_RE);
if (groups) {
name = groups[1];
message = groups[2]; }}const event = {
exception: {
values: [{type: name || 'Error'.value: message,
},
],
},
};
return this._enhanceEventWithInitialFrame(event, url, line, column); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -_enhanceEventWithInitialFrame(event, url, line, column) {
event.exception = event.exception || {};
event.exception.values = event.exception.values || [];
event.exception.values[0] = event.exception.values[0) | | {}; event.exception.values[0].stacktrace = event.exception.values[0].stacktrace || {};
event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames || [];
const colno = isNaN(parseInt(column, 10))?undefined : column;
const lineno = isNaN(parseInt(line, 10))?undefined : line;
const filename = isString(url) && url.length > 0 ? url : getLocationHref();
if (event.exception.values[0].stacktrace.frames.length === 0) {
event.exception.values[0].stacktrace.frames.push({
colno,
filename,
function: '? '.in_app: true,
lineno,
});
}
returnevent; }}Copy the code
Analysis:
- _eventFromIncompleteOnError for basic types of errors, which can be directly obtained error messages, so were direct incoming url parameter, in line and column
- _eventFromIncompleteOnError is
Add error types and information
- _enhanceEventWithInitialFrame is
Adding an error stack
At this point, window.onerror and window.unhandrerejection have been studied for different handling of errors, and the next step is to finally conduct unified processing of error data.
4.7 addExceptionMechanis
AddExceptionMechanism Adds the Mechanism attribute to the Event event
For example, the above example is processed through addExceptionMechanism
addExceptionMechanism(event, {
handled: false.type: 'onerror'});Copy the code
The following data formats are obtained:
exception: {
stacktrace: [
// ...].type: 'Error'.value: 'externalLibrary method broken: 1610364003791'.mechanism: {type:'onerror'.handled:false}}Copy the code
4.8 currentHub.captureEvent
At this point, we have converted the error message into the data format we want.
CurrentHub. CaptureEvent is used to report data, which will be used in the next article to analyze Sentry source code (3).
4.9 Supplementary: How to Handle the Error Information Proactively reported
Except by listening to the window. The window and onerror. Unhandledrejection, report users can also use captureException active fault data
Now let’s see how this is different from handling data when listening
function captureException(exception, hint, scope) {
// ...
this._process(
this._getBackend()
.eventFromException(exception, hint)
// ...
);
returneventId; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --function eventFromException(options, exception, hint) {
const syntheticException = (hint && hint.syntheticException) || undefined;
const event = eventFromUnknownInput(exception, syntheticException, {
attachStacktrace: options.attachStacktrace,
});
addExceptionMechanism(event, {
handled: true.type: 'generic'});// ...
}
Copy the code
Analysis:
- You can see that the eventFromException method is actively reported
- EventFromException is actually a call
4.3 eventFromUnknownInput
Note that syntheticException is not undeFind and can trace the error stack
4.10 summarize
Finally, let’s go through a flow chart to sort out what Sentry does when something goes wrong.
Five, the actual combat
This section gives you an intuitive view of the types of data returned by Sentry through several examples
Sentry. Init configuration
Sentry.init({
dsn: '.. '.attachStacktrace: true.debug: true.enabled: true.release: '.. '});Copy the code
5.1 window. The error | error type is the basic type
Here go window. In the onerror 4.6 _eventFromIncompleteOnError method to process the data
Test cases:
<button id="error-string">window.error | string Error</button>
<script>
document.querySelector('#error-string').addEventListener('click'.() = > {
const script = document.createElement('script');
script.src = './errorString.js';
document.body.appendChild(script);
});
</script>
Copy the code
errorString.js:
function externalLibrary(date) {
throw `string Error${date}`;
}
externalLibrary(Date.now());
Copy the code
Show results:
Corresponding error data:
exception: {
values: {
mechanism: {
handled: false.type: 'onerror',},stacktrace: {
frames: [{colno: 3.filename: 'http://127.0.0.1:5500/packages/browser/examples/errorString.js'.function: '? '.in_app: true.lineno: 2,}]},type: 'Error'.value: 'string Error1610594068361',}},Copy the code
5.2 the window. The error | error type is a reference type
Here go window. In the onerror 4.4 _enhanceEventWithInitialFrame method to process the data, due to walk the eventFromPlainObject eventFromUnknownInput, So there’s no error stack message in there.
Test cases:
<button id="error-throw-new-error">window.error | throw new Error</button>
<script>
document.querySelector('#error-throw-new-error').addEventListener('click'.() = > {
console.log('click');
const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = 'https://rawgit.com/kamilogorek/cfbe9f92196c6c61053b28b2d42e2f5d/raw/3aef6ff5e2fd2ad4a84205cd71e2496a445ebe1d/external-l ib.js';
document.body.appendChild(script);
});
</script>
Copy the code
external-lib.js
function externalLibrary (date) {
throw new Error(`externalLibrary method broken: ${date}`);
}
externalLibrary(Date.now());
Copy the code
Show results:
Corresponding error data:
{
exception: {
values: [{stacktrace: {
mechanism: {
handled: false.type: 'onunhandledrejection',},frames: [{colno: 23.filename: 'http://127.0.0.1:5500/packages/browser/examples/app.js'.function: '? '.in_app: true.lineno: 171,},],},},]; }type: 'Error';
value: 'promise-throw-error';
}
Copy the code
5.3 window. The error | not reporting errors
ShouldIgnoreOnError 4.1 shouldIgnoreOnError 4.1 shouldIgnoreOnError 4.1 shouldIgnoreOnError
The test code
<button id="ignore-message">ignoreError message example</button>
<script>
document.querySelector('#ignore-message').addEventListener('click'.() = > {
fun()
});
</script>
Copy the code
Show results:
5.4 Windows. Unhandledrejection | error type is the basic type
Here go window. In the unhandledrejection 4.5 _eventFromRejectionWithPrimitive method to process the data, there is no news of the error stack.
The test code
<button id="unhandledrejection-string">window.unhandledrejection | string-error</button>
<script>
document.querySelector('#unhandledrejection-string').addEventListener('click'.() = > {
new Promise(function(resolve, reject) {
setTimeout(function() {
return reject('oh string error');
}, 200);
});
});
</script>
Copy the code
Show results:
Corresponding error data:
exception: {
values: [{mechanism: { handled: false.type: 'onunhandledrejection'}},].type: 'UnhandledRejection'.value: 'Non-Error promise rejection captured with value: oh string error'.level: 'error',}Copy the code
5.5 Windows. Unhandledrejection | error type is a reference type
Here go window. In the unhandledrejection 4.3 key: the eventFromPlainObject eventFromUnknownInput, so there is no news of the error stack.
The test code
<button id="unhandledrejection-plainObject">window.unhandledrejection | plainObject</button>
<script>
document.querySelector('#unhandledrejection-plainObject').addEventListener('click'.() = > {
new Promise(function(resolve, reject) {
setTimeout(function() {
const obj = {
msg: 'plainObject'.testObj: {
message: 'This is a test.',},date: new Date(),
reg: new RegExp(),
testFun: () = > {
console.log('testFun'); }};return reject(obj);
}, 200);
});
});
</script>
Copy the code
Show results:
Corresponding error data:
exception: {
values: [{type: 'UnhandledRejection'.value: '"Non-Error promise rejection captured with keys: msg, testFun, testObj"'],},extra: {
__serialized__: {
msg: 'plainObject'.testFun: '[Function: testFun]'.testObj: {
message: 'This is a test.',}}},Copy the code
5.5 Windows. Unhandledrejection | error type is a reference type and is DOMException
Here go window. The unhandledrejection 4.3 key: The eventFromUnknownInput method handles the data, so there is no error stack message in the eventFromString.
The test code
<button id="DOMException">DOMException</button>
<script>
document.querySelector('#DOMException').addEventListener('click'.() = > {
screen.orientation.lock('portrait');
});
</script>
Copy the code
Show results:
Corresponding error data:
exception: {
values: [{type: 'Error'.value: 'NotSupportedError: screen.orientation.lock() is not available on this device.',}]},message: 'NotSupportedError: screen.orientation.lock() is not available on this device.'.tags: {
'DOMException.code': '9',}Copy the code
5.5 Actively Reporting
How to deal with the error information that is actively reported here
The test code
<button id="DOMException">DOMException</button>
<script>
document.querySelector('#capture-exception').addEventListener('click'.() = > {
Sentry.captureException(new Error(`captureException call no. The ${Date.now()}`));
});
</script>
Copy the code
Show results:
The console doesn’t have any errors because sentry. captureException sends an error data to the client
Corresponding error data:
exception: {
event_id: '0e7c9eb8fa3c42b786543317943e9d0d'.exception: {
values: [{mechanism: {
handled: true.type: 'generic',},stacktrace: {
frames: [{colno: 29.filename: 'http://127.0.0.1:5500/packages/browser/examples/app.js'.function: 'HTMLButtonElement.<anonymous>'.in_app: true.lineno: 142,},],},},],type: 'Error'.value: 'captureException call no. 1610599823126',}}Copy the code
Six, summarized
Sentry at the time of initialization of the window. The onerror and window unhandledrejection hijacking, when the error occurred, ComputeStackTrace is used to erase the difference between the information returned by each browser kernel to obtain the error stack. Different types of error messages have their own processing methods, and finally output unified error data.
After Sentr, we get non-error data such as user-Agent, browser information, system information, custom information, and so on, and pass it to the Sentry lifecycle function. Finally, we send the data to the Sentry server for error display.
Vii. Reference materials
- The Sentry’s official website
- The Sentry warehouse
- Error MDN
- ErrorEvent MDN
- DOMException MDN
- Front-end anomaly monitoring. – This is enough
- Talk about front-end monitoring – Error monitoring