错误信息监控简单来说就是要搜集报错信息的发生的位置,以及报错的类型,进行上报,便于我们能够更好的掌握错误信息,从而能够对症下药。按照 5W1H 的法则来说,我们需要关注以下的几项信息:
try / catch
这是我们在代码调试的过程中最常用的一个方式,但它只能捕获代码常规的运行错误,语法错误和异步错误并能捕获到。
// 常规运行时错误,可以捕获 ✅
try {
console.log(notdefined);
} catch(e) {
console.log('捕获到异常:', 'ReferenceError');
}
// 语法错误,不能捕获 ❌
try {
const notdefined,
} catch(e) {
console.log('捕获不到异常:', 'Uncaught SyntaxError');
}
// 异步错误,不能捕获 ❌
try {
setTimeout(() => {
console.log(notdefined);
}, 0)
} catch(e) {
console.log('捕获不到异常:', 'Uncaught ReferenceError');
}
window.onerror
当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror() 。
加载一个全局的 error 事件处理函数可用于自动收集错误报告。
最后需要补充的是:window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生,控制台还是会显示 Uncaught Error 。
```javascript
/**
* @param { string } message 错误信息
* @param { string } source 发生错误的脚本URL
* @param { number } lineno 发生错误的行号
* @param { number } colno 发生错误的列号
* @param { object } error Error对象
*/
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到的错误信息是:', message, source, lineno, colno, error )
}
// 常规运行时错误,可以捕获 ✅
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
console.log(notdefined);
// message: "Uncaught ReferenceError: notdefined is not defined"
// source: "file:///C:/Users/qinzq42866/Desktop/error.html"
// lineno: 14
// colno: 19
// error: ReferenceError: notdefined is not defined at file
// 语法错误,不能捕获 ❌
window.onerror = function(message, source, lineno, colno, error) {
console.log('未捕获到异常:',{message, source, lineno, colno, error});
}
const notdefined,
// Uncaught SyntaxError: Missing initializer in const declaration
// 异步错误,可以捕获 ✅
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
setTimeout(() => {
console.log(notdefined);
}, 0)
// message: "Uncaught ReferenceError: notdefined is not defined"
// source: "file:///C:/Users/qinzq42866/Desktop/error.html"
// lineno: 15
// colno: 21
// error: ReferenceError: notdefined is not defined at file
// 资源错误,不能捕获 ❌
<script>
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
</script>
// GET https://yun.tuia.cn/image/kkk.png 404 (Not Found)
window.addEventListener
当一项静态资源加载失败时,加载资源的元素会触发一个 Event 接口的 Error 事件,这些 Error 事件不会向上冒泡到 window ,但能被捕获。而 window.onerror 不能检测捕获。
// 图片、script、css加载错误,都能被捕获 ✅
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
// fetch错误,不能捕获 ❌
<script>
window.addEventListener('error', (error) => {
console.log('未捕获到异常:', error);
}, true)
</script
<script>
fetch('https://tuia.cn/test')
</script>
由于网络请求异常不会发生事件冒泡,因此必须在事件捕获的阶段将其捕捉到才行,这种方式虽然能够捕捉到网络请求的异常,但是却无法判断 HTTP 的状态,因此仍然需要配合服务端的日志进行配合分析。
需要注意的是:不同浏览器下返回的 Error 对象是不一样的,需要做兼容处理。
没有写 catch 的 Promise 中抛出的错误是无法被 onerror 或 try / catch 捕获到的,这也是为什么我们一定要在 Promise 后面加上 catch 去捕获和处理异常。
为了防止有漏掉的 Promise 异常信息,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error 。
说明:当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;这可能发生在 window 下,但也可能发生在 Worker 中。 这对于调试回退错误处理非常有用。
window.addEventListener("unhandledrejection", event => {
console.warn('UNHANDLED PROMISE REJECTION:', ${event.reason});
});
window.onunhandledrejection = event => {
console.warn('UNHANDLED PROMISE REJECTION:', ${event.reason});
};
window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
});
Promise.reject('promise error');
说明:如果去掉控制台的异常显示,需要加上 event.preventDefault() ;
由于 Vue 会捕获到所有 Vue 单文件组件或者 Vue.extend 继承的代码,所以在 Vue 里面出现的错误并不会直接抛给 window.onerror ,而是会被 Vue 自身的 Vue.config.errorHandler 捕获。
Vue.config.errorHandler = (err, vm, info) => {
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
}
React 16 提供了一个内置函数 componentDidCatch ,使用它可以轻松的捕获到 React 组件内部抛出的错误信息。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, errorInfo) {
console.log('捕获到错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return `Something went wrong.`;
}
return this.props.children;
}
}
根据W3C性能小组引入的新的API(目前IE9以上的浏览器)–window.performance,实现前端性能监控
(function () {
handleAddListener('load', getTiming)
function handleAddListener(type, fn) {
if (window.addEventListener) {
window.addEventListener(type, fn)
} else {
window.attachEvent('on' + type, fn)
}
}
function getTiming() {
try {
var time = performance.timing;
var timingObj = {};
var loadTime = (time.loadEventEnd - time.loadEventStart);
if (loadTime < 0) {
setTimeout(function () {
getTiming();
}, 200);
return;
}
// 阶段耗时
timingObj['DNS解析耗时'] = (time.domainLookupEnd - time.domainLookupStart);
timingObj['TCP连接耗时'] = (time.connectEnd - time.connectStart);
timingObj['SSL安全连接耗时'] = (time.connectEnd - time.secureConnectionStart);//针对https
timingObj['网络请求耗时'] = (time.responseStart - time.requestStart);
timingObj['数据传输耗时'] = (time.responseEnd - time.responseStart);
timingObj['DOM解析耗时'] = (time.domInteractive - time.responseEnd);
timingObj['资源加载耗时, 表示页面中的同步加载资源'] = (time.loadEventStart - time.domContentLoadedEventEnd);
timingObj['前端onload执行时间'] = (time.loadEventEnd - time.loadEventStart);
//性能指标(上报字段名)
timingObj["首次渲染"] = time.responseEnd - time.fetchStart
// timingObj["首屏时间"] = first meaningful paint
timingObj["首次可交互"] = time.domInteractive - time.fetchStart
timingObj["DOMReady"] = time.domContentLoadedEventEnd - time.fetchStart
timingObj["页面完全加载"] = time.loadEventStart - time.fetchStart
timingObj["首包时间"] = time.responseStart - time.domainLookupStart
for (item in timingObj) {
console.log(item + ":" + timingObj[item] + '毫秒(ms)');
}
console.log(performance.timing);
console.log(performance);
} catch (e) {
console.log(timingObj)
console.log(performance.timing);
}
}
})();
import tracker from "../util/tracker";
export function pv() {
tracker.send({
kind: "business",
type: "pv",
startTime: performance.now(),
pageURL: getPageURL(),
referrer: document.referrer,
uuid: getUUID(),
});
let startTime = Date.now();
window.addEventListener(
"beforeunload",
() => {
let stayTime = Date.now() - startTime;
tracker.send({
kind: "business",
type: "stayTime",
stayTime,
pageURL: getPageURL(),
uuid: getUUID(),
});
},
false
);
}
代码埋点
嵌入代码的形式
优点:精确(任意时刻,数据量全面)
缺点:代码工作量点
可视化埋点
通过可视化交互的手段,代替代码埋点
将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个系统,可以在业务代码中自定义的增加埋点事件等等,最后输出的代码耦合了业务代码和埋点代码
用系统来代替手工插入埋点代码
无痕埋点
前端的任意一个事件被绑定一个标识,所有的事件都被记录下来
通过定期上传记录文件,配合文件解析,解析出来我们想要的数据,并生成可视化报告供专业人员分析
无痕埋点的优点是采集全量数据,不会出现漏埋和误埋等现象
缺点是给数据传输和服务器增加压力,也无法灵活定制数据结构
前端监控指的是什么?
前端 监控