In the last article, I talked about how do front-end projects connect to the Sentry monitoring platform?
This article summarizes the principles of Sentry and the use of custom reports
Common types of front-end exceptions
- ECMAScript Execeptions
Developer.mozilla.org/zh-CN/docs/…
- DOMException
Developer.mozilla.org/zh-CN/docs/…
- CDN, img, script, link, audio, video, iframe…
- Performance. GetEntries tries to find an unloaded resource
- Script added crossorigin attribute/server support across domains
- Promise error
Two, common front-end anomaly capture method
- Try catch synchronous/local intrusive, unable to handle syntax errors and asynchrony
/ / can capture
try{
a // Undefined variable
} catch(e){
console.log(e)
}
// Cannot catch -- syntax error
try{
var a = \ 'a'
}catch(e){
console.log(e)
}
// Cannot capture -- asynchronous
try{
setTimeout(() = >{
a
})
} catch(e){
console.log(e)
}
// Add a try catch to setTimeout
Copy the code
- Window. onerror can catch global/run-time errors only, not resource loading errors
// 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 (object)
window.onerror = function(message, source, lineno, colno, error) {... }// Window. onerror cannot capture valid information for js files in different fields. For security reasons, error messages returned by different browsers may have different parameters. Window. onerror cannot catch exception information in many browsers, and script error will be returned. So the script needs to be set up
// crossorigin="anonymous"
Copy the code
- Window.addeventlisenter (‘error’) catches a resource load failure, but the error stack is incomplete
window.addEventListener('error'.function(event) {
if(! e.message){// Network resource loading error}},true)
Copy the code
- unhandledrejection
Promise
Reject is rejected and there is no Reject handlerunhandledrejection
Promise
Reject is rejected and a Reject handler is availablerejectionhandled
window.addEventListener("unhandledrejection".event= > {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
Copy the code
SetTimeout, setInterval, requestAnimationFrame
And so on, using method interception rewrite
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.config.errorHandler
// 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') {
_oldOnError.call(this, error, vm, info); }}; }module.exports = vuePlugin;
Copy the code
- The React of ErrorBoundary
Definition of ErrorBoundary: ** It becomes an error boundary if either (or both) of the life cycle methods static getDerivedStateFromError() or ****componentDidCatch()** is defined in a class component. When an error is thrown, render the standby UI using static getDerivedStateFromError() and print the error message with componentDidCatch()
// 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
- Request to intercept
- XHR override intercepts send and open
- The fetch intercept
- Axios request/response interceptor
- Log interception console.XXX overwritten
- Page crash Handling
window.addEventListener('load'.() = >{
sessionStorage.setTitem('page_exit'.'pending')})window.addEventListener('beforeunload'.() = >{
sessionStorage.setTitem('page_exit'.'true')
})
sessionStorage.getTitem('page_exit')! ='true' // The page crashes
Copy the code
Third, error reporting method
- XHR report
- fetch
- img
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
Copy the code
- navigator.sendBeacon
Using the sendBeacon() method allows the user agent to asynchronously send data to the server when the opportunity presents itself, without delaying the page’s unload or affecting the load performance of the next navigation. This solves all the problems of submitting analysis data: the data is reliable, the transfer is asynchronous, and the next page load is not affected.
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
Copy the code
Four, Sentry implementation principle
- Init initializes, configures information such as release and project DSN, and then mounts the Sentry object to the global object.
- Override the window.onerror method.
When something goes wrong at runtime, js throws an Error object, which can be retrieved through the window.onerror method. Sentry rewrites the window.onerror method using TraceKit to encapsulate browser differences.
- Rewrite window. Onunhandledrejection method.
Because the window. The onerror event without access to promise an unhandled exception, at this moment need to use Windows. Onunhandledrejection method to catch exceptions to report. This method handles the error differently depending on the type of object it receives.
- If you receive an ErrorEvent object, you simply fetch its error property, which is the corresponding error object.
- If a DOMError or DOMException is received, the name and message can be resolved directly, because such errors are usually caused by using the deprecated DOMAPI and do not carry an error stack.
- If a standard error object is received, no processing is done
- If you receive a normal JavaScript object
- Upload using Ajax
When an exception occurs on the front end, Sentry collects the content
-
Exception message: The Sentry automatically captures the error message when an exception is thrown.
-
User information: user information (such as login name, ID, and level), organization information, and company information.
-
Behavior information: The user’s operation process, for example, after logging in to xx page, click XX button, Sentry will automatically capture.
-
Version information: The version information (test, production, grayscale) of the running project, and the version number.
-
Device information: The platform used by the project. Web projects include running device information and browser version information. Mini program items include the phone model, system version and wechat version of the running phone.
-
Timestamp: Records the time when an exception occurred, and Sentry automatically captures it.
-
Exception level: Sentry Levels of exceptions “fatal”, “ERROR “, “warning”, “log”, “info”, “debug”, “critical”
-
Platform information: Record the item where the exception occurs.
The Fetch request is eventually called and reported to the corresponding Sentry server
Sentry configuration
Note: If you are a Vue project, do not use Sentry in your development environment. Since Vue projects use Sentry, @Sentry/Integrations needs to be configured. @Sentry/Integrations is implemented through custom Vue errorHandler hooks, which will stop activating Vue raw logError. Causes errors in Vue components not to be printed in the console. So vUE projects do not use Sentry in the development environment.
// main.js
// Configuration reference
// https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/
Sentry.init({
Vue,
dsn: 'https://[email protected]/4'.tracesSampleRate: 1.0./ / sampling rate
environment: process.env.NODE_ENV, / / environment
release: process.env.SENTRY_RELEASE, / / version number
allowUrls: [].// Whether to match only urls
initialScope: {}, // Set the initialization data
trackComponents: true.// If you want to trace render information for child components
hooks: ["mount"."update"."destroy"].// Can know the record life cycle
beforeSend: (event, hint) = > event, // Preprocess logs before reporting
// Integrate configuration
// It is integrated by default
// InboundFilters
// FunctionToString
// TryCatch
// Breadcrumbs integrates log processing for console logs, DOM operations, FETCH requests, XHR requests, route history, and sentry reports
// GlobalHandlers intercepts the onError and onunHandledrejection events
// LinkedErrors error level, default is 5
// UserAgent
// Dedupe Deduplication
/ / modify system integration integrations: [new Sentry. Integrations. Breadcrumbs (} {the console: false)].
integrations: [
new BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
// tracingOrigins: ['localhost', 'my-site-url.com', /^\//],})],})Copy the code
The integration plug-in SentryRRWeb
Sentry can also record screen information to help developers locate error official documents more quickly. Sentry error recording is mainly implemented by the rrWeb package
- 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
Add filter events and custom logic to hooks such as beforeSend
Sentry.init({
dsn:'https://[email protected]/5706930'.beforeSend(event, hint) {
// Check if it is an exception, and if so, show the report dialog
if (event.exception) {
Sentry.showReportDialog({ eventId: event.event_id });
}
returnevent; }});Copy the code
6. Sentry manual reporting
- Setting global Properties
Sentry.setUser({ email: '[email protected]' }) // Set global variables
Sentry.configureScope((scope) = > scope.clear()); // Clear all global variables
Sentry.configureScope((scope) = > scope.setUser(null)); // Clear the user variable
Sentry.setTag("page_locale"."de-at"); / / global
Copy the code
- Manually capture
The Sentry does not automatically catch caught exceptions: If you write a try/catch and do not rethrow the exception, it will never appear in the Sentry
import * as Sentry from "@sentry/vue";
// Exception catch
try {
aFunctionThatMightFail();
} catch (err) {
Sentry.captureException(err);
}
// Message capture
Sentry.captureMessage("Something went wrong");
// Set the level
/ / level enumerated [" fatal ", "error", "warning", "log", "info", "debug", "critical"]
Sentry.captureMessage("this is a debug message"."debug");
// Set custom content
Sentry.captureMessage("Something went fundamentally wrong", {
contexts: {
text: {
hahah: 22,}},level: Sentry.Severity.Info,
});
// Set the exception level
Sentry.withScope(function(scope) {
scope.setLevel("info");
Sentry.captureException(new Error("custom error"));
});
Outside scope, inherit the previous external level
Sentry.captureException(new Error("custom error 2"));
Set other parameters tags, extra, contexts, user, level, fingerprint
// https://docs.sentry.io/platforms/javascript/guides/vue/enriching-events/context/
// Object / Function
Sentry.captureException(new Error("something went wrong"), {
tags: {
section: "articles",}}); Sentry.captureException(new Error("clean as never"), scope= > {
scope.clear();
// 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 the event grouping rule:
scope.setFingerprint(['{{ default }}', url])
// Set additional data:Scope. SetExtra (' data ', {request: { a: 1.b: 2 })
return scope;
});
// Event grouping
Sentry.configureScope(scope= > scope.setTransactionName("UserListView"));
// Event customization
/ / global
Sentry.configureScope(function(scope) {
scope.addEventProcessor(function(event, hint) {
// TODO
// RETURNING NULL deletes the event
return event;
});
});
// Local event
Sentry.withScope(function(scope) {
scope.addEventProcessor(function(event, hint) {
// TODO
// RETURNING NULL deletes the event
return event;
});
Sentry.captureMessage("Test");
});
Copy the code
- Interface Exception Reporting
// Normal Axios sentry will catch
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
// 1. Manually report a network exception
axios.interceptors.response.use(
(response: AxiosResponse) = > response,
(error: AxiosError) = > {
Sentry.captureException(error)
return Promise.reject(error)
}
)
axios({
url,
method,
})
.then(async (response: AxiosResponse) => {
resolve(response.data)
})
.catch(async (error: AxiosError) => {
Sentry.captureException(error)
reject(error)
})
// 2. Report exceptions
Sentry.captureException(error, {
contexts: {
message: {
url: error.response.config.baseURL + error.response.config.url,
data: error.response.config.data,
method: error.response.config.method,
status: error.response.status,
statusText: error.response.statusText,
responseData: JSON.stringify(error.response.data),
},
},
});
Copy the code
- Crash handling: If the program shuts down unexpectedly, it can be handled in the close event
Sentry.close(2000).then(function() {
// perform something after close
});
Copy the code
7. Performance monitoring
- 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
Sentry is implemented mainly through window.performance
// 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
Reference documentation
- Front-end performance monitoring and error collection and reporting
- Sentry principle – Collect errors and report them
- How does Sentry handle error data
- Check out the business front End Error Monitoring System (Sentry) policy upgrade
- Sentry official documentation – Vue platform