We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前端异常包含很多种情况:1. js 编译时异常(开发阶段就能排)2. js 运行时异常;3. 加载静态资源异常(路径写错、资源服务器异常、CDN 异常、跨域)4. 接口请求异常等
try catch 只能捕获到同步的运行时错误,不能捕获语法和异步错误
try { console.log(a); } catch (e) { console.log('错误捕获', e); }
错误捕获 ReferenceError: a is not defined 2. 语法错误 ❌
try { let a = '3.15; } catch(e) { console.log('错误捕获', e); }
Uncaught SyntaxError: Invalid or unexpected token。 注意这并不是 try catch 捕获到的错误,而是浏览器控制台默认打印出来的
Uncaught SyntaxError: Invalid or unexpected token。
try { setTimeout(() => { a.map((v) => v); }, 1000); } catch (e) { console.log('错误捕获', e); }
Uncaught ReferenceError: a is not defined
JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()
/** * @param {String} message 错误信息 * @param {String} source 出错文件 * @param {Number} lineno 行号 * @param {Number} colno 列号 * @param {Object} error Error对象(对象) */ window.onerror = (message, source, lineno, colno, error) => { console.log('error message:', message); console.log('position', lineno, colno); console.error('错误捕获:', error); return true; // 异常不继续冒泡,浏览器默认打印机制就会取消 };
console.log(a);
输出如下信息
error message: Uncaught ReferenceError: a is not defined 1.html:20 position 25 17 1.html:21 错误捕获: ReferenceError: a is not defined at 1.html:25
setTimeout(() => { arr.map((v) => v); }, 1000);
error message: Uncaught ReferenceError: arr is not defined 1.html:20 position 26 9 1.html:21 错误捕获: ReferenceError: arr is not defined at 1.html:26
<img src="http://a.com/a.png" />
GET http://a.com/a.png net::ERR_NAME_NOT_RESOLVED 不论是静态资源异常,或者接口异常,错误都无法捕获到。
GET http://a.com/a.png net::ERR_NAME_NOT_RESOLVED
tips: window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx
<input type="button" onclick="alert(0);" />
var btn = document.getElementsByClassName('button'); btn.onclick = function () { alert(0); };
存在的问题:
可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用。
为什么没有 1 级:因为 1 级 DOM 标准中并没有定义事件相关的内容,所以没有所谓的 1 级 DOM 事件模型。
跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。 复现过程
<input type="button" value="script error" onclick="a()" /> <script src="http://127.0.0.1:3200/a.js"></script>
a.js 的内容为
function a() { throw new Error('Fail a.js'); }
使用 koa-static
const Koa = require('koa'); const app = new Koa(); const KoaStatic = require('koa-static'); // 静态资源目录对于相对入口文件index.js的路径 // 使用 koa-static 使得前后端都在同一个服务下 app.use(KoaStatic(__dirname)); app.listen(3200, () => { console.log('启动成功'); });
此时用 window.addEventListener('error') 获取到的信息为 script error。
script error
一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下: 跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。
// <script src="http://127.0.0.1:3200/a.js" crossorigin></script> // 或者动态去添加 `js` 脚本: const script = document.createElement('script'); script.crossOrigin = 'anonymous'; script.src = url; document.body.appendChild(script);
这个时候捕获到错误:Uncaught ReferenceError: a is not defined,通过 Network 可以看到 请求 http://127.0.0.1:3200/a.js 出错,CORS error
所以,还需要在服务器端设置:Access-Control-Allow-Origin
const Koa = require('koa'); const app = new Koa(); const KoaStatic = require('koa-static'); const cors = require('@koa/cors'); app.use(cors()); // 静态资源目录对于相对入口文件index.js的路径 // 使用 koa-static 使得前后端都在同一个服务下 app.use(KoaStatic(__dirname)); app.listen(3200, () => { console.log('启动成功'); });
此时再次执行捕获到异常:Uncaught Error: Fail a.js
const originAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, listener, options) { const wrappedListener = function (...args) { try { return listener.apply(this, args); } catch (err) { throw err; } }; return originAddEventListener.call(this, type, wrappedListener, options); };
我们不仅知道异常堆栈,而且还知道导致该异常的事件处理器,是在何处添加进去的。实现这个效果,也很简单:
(() => { const originAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, listener, options) { + // 捕获添加事件时的堆栈 + const addStack = new Error(`Event (${type})`).stack; const wrappedListener = function (...args) { try { return listener.apply(this, args); } catch (err) { + // 异常发生时,扩展堆栈 + err.stack += '\n' + addStack; throw err; } } return originAddEventListener.call(this, type, wrappedListener, options); } })();
同样的道理,我们也可以对 setTimeout、setInterval、requestAnimationFrame 甚至 XMLHttpRequest 做这样的拦截,得到一些我们本来得不到的信息。
上面这种写法只对 DOM2 级的事件起作用,因为我们拦截的是 addEventListener 事件
addEventListener
定义:正常情况下,html 页面中主要包含的静态资源有:js 文件、css 文件、图片文件,这些文件加载失败将直接对页面造成影响甚至瘫痪,所有我们需要把他们统计出来。不太确定是否需要把所有静态资源文件的加载信息都统计下来,既然加载成功了,页面正常了,应该就没有统计的必要了,所以我们只统计加载出错的情况。 收集方法:
var img = document.getElementById('#img'); img.onerror = function (e) { // 捕获错误 console.log(e); };
// 浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。而通过window.performance.getEntries方法,则可以以数组形式,返回这些请求的时间统计信息,每个数组成员均是一个PerformanceResourceTiming对象! function performanceGetEntries() { // 判断浏览器是否支持 if (!window.performance && !window.performance.getEntries) { return false; } var result = []; // 获取当前页面所有请求对应的PerformanceResourceTiming对象进行分析 window.performance.getEntries().forEach((item) => { result.push({ url: item.name, entryType: item.entryType, type: item.initiatorType, 'duration(ms)': item.duration, }); }); // 控制台输出统计结果 console.table(result); // 表示已经加载的资源 // 然后把整个资源的数量减去已经加载好的资源,剩下的就是没有加载出来的资源的数量。 }
/** * 监控页面静态资源加载报错 */ function loadResourceError() { window.addEventListener( 'error', function (e) { console.log(e, '错误捕获==='); if (e) { let target = e.target || e.srcElement; let isElementTarget = target instanceof HTMLElement; if (!isElementTarget) { // js错误 console.log('js错误==='); // js error处理 let { filename, message, lineno, colno, error } = e; let { message: ErrorMsg, stack } = error; } else { // 页面静态资源加载错误处理 console.log('资源加载错误==='); let { type, timeStamp, target } = e; let { localName, outerHTML, tagName, src } = target; let typeName = target.localName; let sourceUrl = ''; if (typeName === 'link') { sourceUrl = target.href; } else if (typeName === 'script') { sourceUrl = target.src; } alert('资源加载失败,请刷新页面或切换网络重试。(' + sourceUrl + ')'); } } // 设为true表示捕获阶段调用,会在元素的onerror前调用,在window.addEventListener('error')后调用 }, true, ); } // 我们根据e.target的属性来判断它是link标签,还是script标签。目前只关注只监控了css,js文件加载错误的情况。
/** * 监控页面静态资源加载报错 */ function recordResourceError() { // 当浏览器不支持 window.performance.getEntries 的时候,用下边这种方式 window.addEventListener( 'error', function (e) { var typeName = e.target.localName; var sourceUrl = ''; if (typeName === 'link') { sourceUrl = e.target.href; } else if (typeName === 'script') { sourceUrl = e.target.src; } var resourceLoadInfo = new ResourceLoadInfo(RESOURCE_LOAD, sourceUrl, typeName, '0'); resourceLoadInfo.handleLogInfo(RESOURCE_LOAD, resourceLoadInfo); }, true, ); }
我们根据报错是的 e.target 的属性来判断它是 link 标签,还是 script 标签。由于目前我关注对前端造成崩溃的错误,所以目前只监控了 css,js 文件加载错误的情况。
要做实时监控和预警,还需要知道更多详细的信息 解决方案
window.addEventListener(‘error’)与 window.onerror 的异同点在于:
如:一段时间内,应用 JS 报错的走势(chart 图表)、JS 错误发生率、JS 错误在 PC 端发生的概率、JS 错误在 IOS 端发生的概率、JS 错误在 Android 端发生的概率,以及 JS 错误的归类。然后,我们再去其中的 Js 错误进行详细的分析,辅助我们排查出错的位置和发生错误的原因。
如:JS 错误类型、 JS 错误信息、JS 错误堆栈、JS 错误发生的位置以及相关位置的代码;JS 错误发生的几率、浏览器的类型,版本号,设备机型等等辅助信息 为了得到这些数据,我们需要在上传的时候将其分析出来。在众多日志分析中,很多字段及功能是重复通用的,所以应该将其封装起来。
// 设置日志对象类的通用属性 function setCommonProperty() { this.happenTime = new Date().getTime(); // 日志发生时间 this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的唯一标识(一个项目对应一个) this.simpleUrl = window.location.href.split('?')[0].replace('#', ''); // 页面的url this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效 this.pageKey = utils.getPageKey(); // 用于区分页面,所对应唯一的标识,每个新页面对应一个值 this.deviceName = DEVICE_INFO.deviceName; this.os = DEVICE_INFO.os + (DEVICE_INFO.osVersion ? ' ' + DEVICE_INFO.osVersion : ''); this.browserName = DEVICE_INFO.browserName; this.browserVersion = DEVICE_INFO.browserVersion; // TODO 位置信息, 待处理 this.monitorIp = ''; // 用户的IP地址 this.country = 'china'; // 用户所在国家 this.province = ''; // 用户所在省份 this.city = ''; // 用户所在城市 // 用户自定义信息, 由开发者主动传入, 便于对线上进行准确定位 this.userId = USER_INFO.userId; this.firstUserParam = USER_INFO.firstUserParam; this.secondUserParam = USER_INFO.secondUserParam; } // JS错误日志,继承于日志基类MonitorBaseInfo function JavaScriptErrorInfo(uploadType, errorMsg, errorStack) { setCommonProperty.apply(this); this.uploadType = uploadType; this.errorMessage = encodeURIComponent(errorMsg); this.errorStack = errorStack; this.browserInfo = BROWSER_INFO; } JavaScriptErrorInfo.prototype = new MonitorBaseInfo();
JS 错误发生率 = JS 错误个数(一次访问页面中,所有的 js 错误都算一次)/PV (PC,IOS,Android 平台同理)
如果拦截 ajax 请求
如何拦截 fetch 请求,如何处理 http code 为 200 时,接口返回的结构体中的错误
如何监控前端接口请求? 万变不离其宗,他们都是对浏览器的这个对象 window.XMLHttpRequest 或者 fetch 进行了封装,所以我们只要能够监听到这个对象的一些事件,就能够把请求的信息分离出来。
/** * 页面接口请求监控 */ function recordHttpLog() { // 监听ajax的状态 function ajaxEventTrigger(event) { var ajaxEvent = new CustomEvent(event, { detail: this, }); window.dispatchEvent(ajaxEvent); } var oldXHR = window.XMLHttpRequest; function newXHR() { var realXHR = new oldXHR(); realXHR.addEventListener( 'loadstart', function () { ajaxEventTrigger.call(this, 'ajaxLoadStart'); }, false, ); realXHR.addEventListener( 'loadend', function () { ajaxEventTrigger.call(this, 'ajaxLoadEnd'); }, false, ); // 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。 // realXHR.onerror = function () { // siftAndMakeUpMessage("Uncaught FetchError: Failed to ajax", WEB_LOCATION, 0, 0, {}); // } return realXHR; } var timeRecordArray = []; window.XMLHttpRequest = newXHR; window.addEventListener('ajaxLoadStart', function (e) { var tempObj = { timeStamp: new Date().getTime(), event: e, }; timeRecordArray.push(tempObj); }); window.addEventListener('ajaxLoadEnd', function () { for (var i = 0; i < timeRecordArray.length; i++) { if (timeRecordArray[i].event.detail.status > 0) { var currentTime = new Date().getTime(); var url = timeRecordArray[i].event.detail.responseURL; var status = timeRecordArray[i].event.detail.status; var statusText = timeRecordArray[i].event.detail.statusText; var loadTime = currentTime - timeRecordArray[i].timeStamp; if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return; var httpLogInfoStart = new HttpLogInfo( HTTP_LOG, url, status, statusText, '发起请求', timeRecordArray[i].timeStamp, 0, ); httpLogInfoStart.handleLogInfo(HTTP_LOG, httpLogInfoStart); var httpLogInfoEnd = new HttpLogInfo( HTTP_LOG, url, status, statusText, '请求返回', currentTime, loadTime, ); httpLogInfoEnd.handleLogInfo(HTTP_LOG, httpLogInfoEnd); // 当前请求成功后就在数组中移除掉 timeRecordArray.splice(i, 1); } } }); }
// 设置日志对象类的通用属性 function setCommonProperty() { this.happenTime = new Date().getTime(); // 日志发生时间 this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的唯一标识(一个项目对应一个) this.simpleUrl = window.location.href.split('?')[0].replace('#', ''); // 页面的url this.completeUrl = utils.b64EncodeUnicode(encodeURIComponent(window.location.href)); // 页面的完整url this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效, // 用户自定义信息, 由开发者主动传入, 便于对线上问题进行准确定位 var wmUserInfo = localStorage.wmUserInfo ? JSON.parse(localStorage.wmUserInfo) : ''; this.userId = utils.b64EncodeUnicode(wmUserInfo.userId || ''); this.firstUserParam = utils.b64EncodeUnicode(wmUserInfo.firstUserParam || ''); this.secondUserParam = utils.b64EncodeUnicode(wmUserInfo.secondUserParam || ''); } // 接口请求日志,继承于日志基类MonitorBaseInfo function HttpLogInfo(uploadType, url, status, statusText, statusResult, currentTime, loadTime) { setCommonProperty.apply(this); this.uploadType = uploadType; // 上传类型 this.httpUrl = utils.b64EncodeUnicode(encodeURIComponent(url)); // 请求地址 this.status = status; // 接口状态 this.statusText = statusText; // 状态描述 this.statusResult = statusResult; // 区分发起和返回状态 this.happenTime = currentTime; // 客户端发送时间 this.loadTime = loadTime; // 接口请求耗时 }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前端异常包含很多种情况:1. js 编译时异常(开发阶段就能排)2. js 运行时异常;3. 加载静态资源异常(路径写错、资源服务器异常、CDN 异常、跨域)4. 接口请求异常等
异常监控具体指标
各种概率计算
try catch
try catch 只能捕获到同步的运行时错误,不能捕获语法和异步错误
错误捕获 ReferenceError: a is not defined 2. 语法错误 ❌
Uncaught SyntaxError: Invalid or unexpected token。
注意这并不是 try catch 捕获到的错误,而是浏览器控制台默认打印出来的
Uncaught ReferenceError: a is not defined
window.onerror
JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()
输出如下信息
输出如下信息
GET http://a.com/a.png net::ERR_NAME_NOT_RESOLVED
不论是静态资源异常,或者接口异常,错误都无法捕获到。
tips: window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx
补充 DOM0 级事件和 DOM2 级事件的区别
存在的问题:
addEvenetListener()、removeEventListener() 有三个参数:
第一个参数是事件名(如 click, IE 是 onclick);
第二个参数是事件处理程序函数;
第三个参数如果是 true 则表示在捕获阶段调用,为 false 表示在冒泡阶段调用。
可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用。
为什么没有 1 级:因为 1 级 DOM 标准中并没有定义事件相关的内容,所以没有所谓的 1 级 DOM 事件模型。
script error
跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。
复现过程
a.js 的内容为
使用 koa-static
此时用 window.addEventListener('error') 获取到的信息为
script error
。一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:
跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。
这个时候捕获到错误:Uncaught ReferenceError: a is not defined,通过 Network 可以看到 请求 http://127.0.0.1:3200/a.js 出错,CORS error
所以,还需要在服务器端设置:Access-Control-Allow-Origin
此时再次执行捕获到异常:Uncaught Error: Fail a.js
或者加载下面一段 JS,可以让我们在没有跨域头的情况下,拿到 4000 按钮事件处理器的执行异常信息。
我们不仅知道异常堆栈,而且还知道导致该异常的事件处理器,是在何处添加进去的。实现这个效果,也很简单:
同样的道理,我们也可以对 setTimeout、setInterval、requestAnimationFrame 甚至 XMLHttpRequest 做这样的拦截,得到一些我们本来得不到的信息。
上面这种写法只对 DOM2 级的事件起作用,因为我们拦截的是
addEventListener
事件参考
静态资源加载
定义:正常情况下,html 页面中主要包含的静态资源有:js 文件、css 文件、图片文件,这些文件加载失败将直接对页面造成影响甚至瘫痪,所有我们需要把他们统计出来。不太确定是否需要把所有静态资源文件的加载信息都统计下来,既然加载成功了,页面正常了,应该就没有统计的必要了,所以我们只统计加载出错的情况。
收集方法:
performance.getEntries() 用来统计静态资源相关的时间信息,返回一个数组,数组的每个元素代表对应的静态资源的信息。
属性介绍
我们根据报错是的 e.target 的属性来判断它是 link 标签,还是 script 标签。由于目前我关注对前端造成崩溃的错误,所以目前只监控了 css,js 文件加载错误的情况。
要做实时监控和预警,还需要知道更多详细的信息
解决方案
window.addEventListener(‘error’)与 window.onerror 的异同点在于:
注意:
JS 错误监控
如:一段时间内,应用 JS 报错的走势(chart 图表)、JS 错误发生率、JS 错误在 PC 端发生的概率、JS 错误在 IOS 端发生的概率、JS 错误在 Android 端发生的概率,以及 JS 错误的归类。然后,我们再去其中的 Js 错误进行详细的分析,辅助我们排查出错的位置和发生错误的原因。
如:JS 错误类型、 JS 错误信息、JS 错误堆栈、JS 错误发生的位置以及相关位置的代码;JS 错误发生的几率、浏览器的类型,版本号,设备机型等等辅助信息
为了得到这些数据,我们需要在上传的时候将其分析出来。在众多日志分析中,很多字段及功能是重复通用的,所以应该将其封装起来。
JS 错误发生率 = JS 错误个数(一次访问页面中,所有的 js 错误都算一次)/PV (PC,IOS,Android 平台同理)
前端接口监控
ajax
如果拦截 ajax 请求
fetch
如何拦截 fetch 请求,如何处理 http code 为 200 时,接口返回的结构体中的错误
如何监控前端接口请求?
万变不离其宗,他们都是对浏览器的这个对象 window.XMLHttpRequest 或者 fetch 进行了封装,所以我们只要能够监听到这个对象的一些事件,就能够把请求的信息分离出来。
The text was updated successfully, but these errors were encountered: