异常监控-异常捕获
2020-07-28 11:15:282021-02-20 17:14:19
异常分类
- 编译时异常/运行时异常
着重处理运行时异常
要处理的异常
- js运行时异常
- 静态资源加载异常
- Promise异常
- ajax请求异常
- 崩溃和卡顿
捕获异常
普通前端项目
// monitor.ts
interface ConfigI {
reportUrl: string;
}
interface BaseinfoI {
title: string;
location: string;
message: string;
kind: string;
type: string;
errorType: string;
}
interface JSRunTimeErrorEventI extends BaseinfoI {
filename: string;
position: string;
stack: string;
selector: string;
}
interface AssetsErrorI extends BaseinfoI {
url: string;
nodeName: string;
}
interface AjaxErrorEventI extends BaseinfoI {
response: string;
status: number;
method: string;
url: string;
}
interface PromiseErrorI extends BaseinfoI {
message: string;
stack: string;
}
const config: ConfigI = { reportUrl: '/' };
function getLastEvent(): undefined | Event {
let lastEvent;
['click', 'touchstart', 'mousedown', 'keydown', 'mouseover'].forEach((eventType) => document.addEventListener(eventType, (event) => {
lastEvent = event;
}, {
capture: true,
passive: true,
}));
return lastEvent;
}
function getSelector(path: Array<EventTarget>) {
return '';
}
function report<T>(data: { type: 'JSRunTimeErrorI' | 'PromiseErrorT' | 'AssetsErrorI' | 'AjaxErrorEventI'; info: T }) {
const image = new Image();
image.src = `${config.reportUrl}?error=${JSON.stringify(data.info)}`;
}
function getCommonInfoFromEvent(event?: Event) {
return {
title: document.title.replace(/&/, ''),
location: window.location.href.replace(/&/, ''),
kind: 'stability',
type: 'error',
};
}
function getLines(stack: string | undefined) {
return stack?.split('\n').slice(1).map((item) => item.replace(/^\s+at\s+/g, '')).join('') || '';
}
// Promise异常
function promiseError() {
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
event.preventDefault();
let message = '';
let stack = '';
message = event.reason;
const { reason } = event;
if (reason instanceof Error) {
message = reason.message;
stack = getLines(reason.stack);
}
report<PromiseErrorI>({ type: 'PromiseErrorT', info: { errorType: event.type, message, stack, ...getCommonInfoFromEvent() } });
return true;
}, true);
}
// 静态资源加载异常&JSRuntime异常
function assetsError() {
window.addEventListener('error', (event: ErrorEvent) => {
const { target } = event;
console.log(event);
const isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
if (!isElementTarget) {
const { message, filename, lineno, colno, error, type } = event;
const position = `${lineno}:${colno}`;
const stack = getLines(error instanceof Error ? error.stack : '');
const lastEvent = getLastEvent();
const selector = lastEvent ? getSelector(lastEvent.composedPath()) : '';
report<JSRunTimeErrorEventI>({
type: 'JSRunTimeErrorI',
info: { ...getCommonInfoFromEvent(), message, errorType: type, filename, position, stack, selector },
});
} else {
let url = '';
let nodeName = '';
if (target instanceof HTMLImageElement || target instanceof HTMLScriptElement) {
url = target.src;
nodeName = target.nodeName;
}
if (target instanceof HTMLLinkElement) {
url = target.href;
nodeName = target.nodeName;
}
report<AssetsErrorI>({ type: 'AssetsErrorI',
info: {
url,
errorType: event.type,
nodeName,
message: '',
...getCommonInfoFromEvent() } });
}
return true;
}, true);
}
// ajax请求异常
function ajaxError() {
const { protocol } = window.location;
if (protocol === 'file:') return;
if (!window.XMLHttpRequest) return;
const xmlReq = window.XMLHttpRequest;
const oldSend = xmlReq.prototype.send;
const oldOpen = xmlReq.prototype.open;
const oldArgs = { method: '', url: '' };
function handleEvent(event: any) {
try {
if (event && event.currentTarget && event.currentTarget.status !== 200) {
const { response, status, statusText } = event.currentTarget;
const { method, url } = oldArgs;
console.log(event);
report<AjaxErrorEventI>({
type: 'AjaxErrorEventI',
info: {
response: JSON.parse(response),
status,
method,
url,
message: statusText,
errorType: event.type,
...getCommonInfoFromEvent(),
},
});
}
} catch (e) {
console.log(`Tool's error: ${e}`);
}
}
xmlReq.prototype.send = function (...args) {
this.addEventListener('error', handleEvent);
this.addEventListener('load', handleEvent);
this.addEventListener('abort', handleEvent);
return oldSend.apply(this, args);
};
xmlReq.prototype.open = function (...args: any) {
const [method, url] = args;
Object.assign(oldArgs, { method, url });
oldOpen.apply(this, args);
};
}
function init(_config: ConfigI) {
Object.assign(config, _config);
promiseError();
assetsError();
ajaxError();
}
export default init;
基于Vue.js的项目
针对于Vue.js项目,虽然官方提供有Vue.config.errorHandler
来拦截内部错误(生命周期、事件绑定等)。但处理能力有限,无法处理js运行时异步异常(如setTimeout)和资源加载异常等,因此需要使用mointor.js
+ Vue.config.errorHandler
来共同处理
基于React.js的项目
引入
monitor.ts
添加
MErrorBoundary
组件用户出现异常后优雅降级显示和收集出错组件
import React from 'react';
class MErrorBoundary extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
console.log('MErrorBoundary');
return { hasError: true };
}
componentDidCatch(error: any, errorInfo: any) {
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>Something went wrong.</h1>
</div>
);
}
return this.props.children;
}
}
export default MErrorBoundary;
白屏和卡顿
// todo