前言
翻看自己前几年的文章,发现写了两篇前端监控的设计方案,但是没有后续了所以现在我们来完成整个系列的文章更新,我们计划分为三个部分,分别是前端错误监控、前端接口监控、前端流量监控、前端性能监控四个部分去一步步搭建我们自己的前端监控SDK。

工具意义
对于 研发/运维人员他们希望能够对前端应用运行情况进行监控及分析,提升应用可观测性,而我们的前端监控恰好可以提供这个能力,前端研发人员无需修改一行代码,即可实现:可根据需要手动上报定义日志,解决前端日志上报诉求,从而自实现可根据日志追溯前端应用运行情况自动上报错误日志,并提供实时预警及定位分析能力,从而缩短问题发现/定位时长、提前洞察系统潜在隐患;自动在业务服务请求中插入会话信息,实现前后端链路聚合能力,从而提升跨域跨请求的链路监控分析能力。
错误监控
前端的错误分为javascript的执行错误、promise异常和资源加载错误。

首先我们先确定我们的数据结构:
typescript
// 日志信息
export interface ILogContext {
level: string, // 错误级别
errorType: string, // 错误类型
message: string, // 错误信息
logCreatTime?: string,// 错误生成时间
logCode?: string // 自定义响应码
version: string, // 组件版本号
globalSessionId: string, // 全局会话标识
uniqueVisitorId: string, // 独立访客标识
systemName: string, // 系统简称
serviceName: string, // 服务名称
referer?: string, // 引用
userAgent: string, // 浏览器用户代理(浏览器标识)
ip?: string, // ip地址
generateTime: string | number, // 发生时间
url: string, // 页面地址
page:string,
domain: string, // 页面域名
title: string, // 页面标题
logGenerateTime: string | number
}
数据结构确定好以后我们开始采集我们的数据
javascript执行错误
js
export const jsErrorsHandler = (options: IParams) => {
window.onerror = (message, source, lineno, colno, error) => {
let msg = createMsgData({
source: source,
message: message,
lineno: lineno,
colno: colno,
error: error
}, options, ERROR_TYPE.JS)
exceptionReport(options.logContextReportUrl, msg);
}
}
promise异常
js
export const globalPromiseHandler = (options: IParams) => {
window.addEventListener("unhandledrejection", event => {
const msg = createMsgData(event, options, ERROR_TYPE.PROMISE)
exceptionReport(options.logContextReportUrl, msg);
})
}
资源加载错误
js
// 监听静态资源加载错误
export const resourceErrorsHandler = (options: IParams) => {
try {
const onResourceHandle = (eventError: ErrorEvent) => {
if (options.errorMsgIsEnable) {
const target = eventError.target || eventError.srcElement;
let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
if (!isElementTarget) return false;
const src = target["src"] || target["href"];
src && createMsgData(eventError, options, ERROR_TYPE.RESOURCE)
}
on(_global, EVENTTYPES.ERROR, onResourceHandle, true);
}
} catch (e) {
console.error("resourceErrorsHandler", e);
}
}
到这一步我们能够采集大部分前端的错误异常。
对于Vue, 那么如何采集我们框架中的异常呢?我们可以如下操作:
typescript
const vueErrorHandler = (options: IParams) => {
// @ts-ignore
let Vue = options.vue;
// @ts-ignore
if (!Vue || !Vue.config) return;
let _oldOnError = Vue.config.errorHandler;
Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
// 上报
const msg = createMsgData(error, {
globalSessionId: options.globalSessionId,
uniqueVisitorId: options.uniqueVisitorId,
systemName: options.systemName,
serviceName: options.serviceName,
apiRequestIsEnable: options.apiRequestIsEnable ? options.apiRequestIsEnable : true, // 用启否是求请口接
errorMsgIsEnable: options.errorMsgIsEnable ? options.errorMsgIsEnable : true, // 错误日志是否启用
sampleRate: options.sampleRate || 10, // 采样率
}, ERROR_TYPE.VUE);
exceptionReport(options.logContextReportUrl, msg);
//
if (typeof _oldOnError === 'function') {
// 拦截Vue.config.errorHandler
// @ts-ignore
_oldOnError.call(this, error, vm, info);
}
Vue.mixin({
beforeCreate() {
registerVue(this)
registerVuex(this)
}
})
};
}
const registerActionHandle = actions => {
Object.keys(actions).forEach(key => {
let fn = actions[key];
actions[key] = function () {
let args = Array.prototype.slice.call(arguments, this.length);
let ret = fn.apply(this, args);
if (isPromise(ret)) {
return ret.catch(vueErrorHandler);
} else { // 默认错误处理
return ret;
}
}
})
}
// 对vuex的错误处理
const registerVuex = (instance: any) => {
if (instance.$options['store']) {
let actions = instance.$options['store']['_actions'] || {}
if (actions) {
let tempActions = {}
Object.keys(actions).forEach(key => {
tempActions[key] = actions[key][0];
})
registerActionHandle(tempActions);
}
}
}
// 对vue的错误处理
const registerVue = (instance: any) => {
if (instance.$options.methods) {
let actions = instance.$options.methods || {};
if (actions) {
registerActionHandle(actions);
}
}
};
export default vueErrorHandler;
对于react我们又是如何操作呢?
typescript
import React, {Component} from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
// state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: true });
// 将异常信息上报给服务器
// logErrorToMyService(error, info);
}
render() {
// @ts-ignore
if (this.state.hasError) {
return '出错了';
}
return this.props.children;
}
}
到此我们就完成了我们对前端错误日志信息的采集。