前端异常监控调研总结

一.异常监控系统

前端和后端处于同一个监控系统中,前端有自己的监控方案,后端也有自己的监控方案,但两者并不分离,因为一个用户在操作应用的过程中如果出现异常,有可能是前端引起,也有可能是后端引起,需要有一个机制,将前后端串联起来,使监控本身统一于监控系统。因此,即使只讨论前端异常监控,其实也不能严格区分前后端界限,而要根据实际系统的设计,在最终的报表中体现出监控对开发和业务的帮助。
  一般而言,一个监控系统,大致可以分为四个阶段:日志采集。日志存储、统计与分析、报告和警告。

二.异常情况

前端异常是指用户使用 Web
应用时无法快速得到符合预期结果的情况,不同的异常带来的后果程度不同,轻则引起用户使用不悦,重则导致产品无法使用,使用户丧失对产品的认可。
后端异常是指使服务无法正常运行的错误或外部问题,目前系统常见异常有数据库入库错误、数据库链接异常、数据格式错误等。后端程序运行的各类报错会将错误信息返回给前端。

三.异常表现分类

根据异常代码的后果程度,对前端异常的表现分为如下几类

  • a. 出错 界面呈现的内容与用户预期的内容不符,例如点击进入非目标界面,数据不准确,出现的错误提示不可理解,界面错位,提交后跳转到错误界面等。
  • b. 呆滞 界面出现操作后没有反应的现象,例如点击按钮无法提交,提示成功后无法继续操作。
  • c. 损坏 界面出现无法实现操作目的的现象,例如点击无法进入目标界面,点击无法查看详情内容等。
  • d. 假死 界面出现卡顿,无法对任何功能进行使用的现象。例如用户无法登录导致无法使用应用内功能,由于某个遮罩层阻挡且不可关闭导致无法进行任何后续操作。
  • e. 崩溃 应用出现经常性自动退出或无法操作的现象。例如间歇性crash,网页无法正常加载或加载后无法进行任何操作。

四.Web前端监控分类

(1)性能监控
性能监控主要是监听前端项目在用户端展示的性能,这将直接关乎到用户的体验效果。监控这些数据,将能够是我们更好的去优化用户体验。常见的性能监控指标包括以下数据:

  1. 不同用户和不同设备下的首屏加载时间,包括白屏时间;
  2. HTTP 接口的响应时间;
  3. 静态资源、包括图片的下载时间。

google 开发者提出了一种 RAIL 模型来衡量应用性能,即:Response、Animation、Idle、Load,分别代表着
web 应用生命周期的四个不同方面。并指出最好的性能指标是:100ms 内响应用户输入;动画或者滚动需在 10ms内产生下一帧;最大化空闲时间;页面加载时长不超过 5 秒。 我们转化为三个方面来看:响应速度、页面稳定性、外部服务调用
响应速度:
页面初始访问速度 + 交互响应速度
页面稳定性:页面出错率
外部服务调用:网络请求访问速度
1.页面访问速度:白屏、首屏时间、可交互时间
1)first paint (FP) and first contentful paint (FCP) 首次渲染、首次有内容的渲染 这两个指标浏览器已经标准化了,从 performance 的 The Paint Timing
API 可以获取到,一般来说两个时间相同,但也有情况下两者不同。
(2)First meaningful paint and hero
element timing 首次有意义的渲染、页面关键元素 我们假设当一个网页的 DOM
结构发生剧烈的变化的时候,就是这个网页主要内容出现的时候,那么在这样的一个时间点上,就是首次有意义的渲染。这个指标浏览器还没有规范,毕竟很难统一一个标准来定义网站的主体内容。
(3)Time to interactive 可交互时间
(4)长任务
浏览器是单线程的,如果长任务过多,那必然会影响着用户响应时长。好的应用需要最大化空闲时间,以保证能最快响应用户的输入。

2.页面稳定性:页面出错情况 资源加载错误 JS 执行报错
3.外部服务调用 CGI 耗时 CGI 成功率 CDN 资源耗时

五. 监控分类解析

在讲如何监控之前,先来看看浏览器提供的 performance api,这也是性能监控数据的主要来源。 performance
提供高精度的时间戳,精度可达纳秒级别,且不会随操作系统时间设置的影响。 目前市场上的支持情况:主流浏览器都支持,大可放心使用。 基于
performance 我们可以测量如下几个方面:

mark、measure、navigation、resource、paint、frame。
let p = window.performance.getEntries();
1.重定向次数:performance.navigation.redirectCount
2.JS 资源数量: p.filter(ele => ele.initiatorType === "script").length
3.CSS 资源数量:p.filter(ele => ele.initiatorType === "css").length
4.AJAX 请求数量:p.filter(ele => ele.initiatorType === "xmlhttprequest").length
5.IMG 资源数量:p.filter(ele => ele.initiatorType === "img").length
6.总资源数量: window.performance.getEntriesByType("resource").length
7.不重复的耗时时段区分:
重定向耗时: redirectEnd - redirectStart
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
HTML 下载耗时:responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
8.其他组合分析:
白屏时间: domLoading - fetchStart
粗略首屏时间: loadEventEnd - fetchStart 或者 domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
9.JS 总加载耗时:
1.const p = window.performance.getEntries();
2.let cssR = p.filter(ele => ele.initiatorType === "script");
3.Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));
10.CSS 总加载耗时:
1.const p = window.performance.getEntries();
2.let cssR = p.filter(ele => ele.initiatorType === "css");
3.Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));

六 错误分类和捕获

错误监控主要是指代码在个别特殊场景下,会出现异常报错,很有可能会引发线上的故障。而这部分异常,如果没有异常监控,则只能在用户发现的情况下进行选择性的上报,并不能让我们及时去发现和解决问题。这一部分的错误信息主要分为下面几类:
 - JS 语法错误、代码异常;
 - 静态资源加载错误;
 - AJAX 请求错误;
 - Promise 异步函数错误;
 - Vue错误;
 - 跨域 Script error;
 - 崩溃和卡顿。

1、JS 语法错误、代码异常

大部分的语法和代码问题,在开发和测试的时候已经排查掉了,但是不排除有一些特殊场景下出现的漏网之鱼,所以这个问题也不能忽视。

使用try-catch try catch
finally只能捕获运行时的错误,无法捕获语法错误,可以拿到出错的信息,堆栈,出错的文件、行号、列号。try catch
finally语句标记要尝试的语句块,并指定一个出现异常时抛出的响应。

try {
  let name = 'aaa';
  console.log(nam);
} catch(e) {
  console.log('捕获到异常:', e);
}
 
控制台:
捕获到异常: ReferenceError: nam is not defined
    at <anonymous>:3:15
window.onerror
window.onerror可以捕捉语法错误,也可以捕捉运行时错误,可以拿到出错的信息,堆栈,出错的文件、行号、列号,只要在当前window执行的Js脚本出错都会捕捉到,通过window.onerror可以实现前端的错误监控。出于安全方面的考虑,当加载自不同域的脚本中发生语法错误时,语法错误的细节将不会报告。

/*
    message:错误信息(字符串)。
    source:发生错误的脚本URL(字符串)
    lineno:发生错误的行号(数字)
    colno:发生错误的列号(数字)
     error:Error对象(对象)
    若该函数返回true,则阻止执行默认事件处理函数。
*/
window.onerror = function(message, source, lineno, colno, error) { 
    // onerror_statements
}
不论是静态资源异常,或者接口异常,语法错误都无法捕获到。

2、静态资源加载错误

window.addEventListener

当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror()
处理函数。这些 error 事件不会向上冒泡到 window ,不过(至少在 Firefox
中)能被单一的window.addEventListener 捕获。

/*
    ErrorEvent类型的event包含有关事件和错误的所有信息。
*/
window.addEventListener('error', function(event) { 
    // onerror_statements
    })
需要注意:

不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
需要注意避免 addEventListener 重复监听。
在vue中使用errorHandler
app.config.errorHandler = (err, vm, info) => {
  // 处理错误
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
或者生命周期钩子 errorCaptured

3、AJAX请求异常

在响应拦截器中处理。 请求错误可以分为有响应的异常和无响应的异常

有响应:业务处理逻辑出错、数据库出错等;这种错误会收到服务端的错误码和对应的错误信息

无响应:40x,50x,网络错误,请求超时等

instance.interceptors.response.use(
  (res) => {},  (error) => {
    // 处理错误逻辑
})

4、promise异常使用Promise Catch

没有写 catch 的 Promise 中抛出的错误无法被 onerror 或 try-catch 捕获到,所以我们务必要在 Promise
中不要忘记写 catch 处理抛出的异常

解决方案: 为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection
的监听,用来全局监听Uncaught Promise Error。使用方式:

window.addEventListener("unhandledrejection", function(e){
  console.log(e);
});

5、Vue 错误

由于 Vue 会捕获到所有 Vue 单文件组件或者 Vue.extend 继承的代码,所以在 Vue 里面出现的错误并不会直接抛给
window.onerror ,而是会被 Vue 自身的 Vue.config.errorHandler 捕获。
如果开发者没有配置Vue.config.errorHandler,那么捕获到的错误会以console.error的方式输出。

Vue.config.errorHandler = error => {
  monitor.errors.push({time: new Date().getTime(), content: error.stack})
}

6、跨域Script error 脚本错误

因为我们在线上的版本,经常做静态资源 CDN
化,这就会导致我们常访问的页面跟脚本文件来自不同的域名,这时候如果没有进行额外的配置,就会容易产生 Script error

Script error
是浏览器在同源策略限制下产生的,浏览器处于对安全性上的考虑,当页面引用非同域名外部脚本文件时中抛出异常的话,此时本页面是没有权利知道这个报错信息的,取而代之的是输出
Script error 这样的信息

7、崩溃和卡顿

卡顿也就是网页暂时响应比较慢, JS 可能无法及时执行。但崩溃就不一样了,网页都崩溃了,JS
都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢?

1.利用 window 对象的 load 和 beforeunload 事件实现网页崩溃的监控。

2.也可以使用 Service Worker 来实现网页崩溃的监控

8、其他错误

网络请求失败 重写 window.XMLHttpRequest 和 window.fetch 捕获请求错误 iframe 错误
父窗口直接使用 window.onerror 是无法直接捕获,通过直接给 iframe 添加 onerror 事件即可。
window.frames[0].onerror

七 小程序异常监控

web端与小程序错误监控差异

在 Web 端监测的是页面完整的 url,而小程序端监测的是路由地址;
小程序页面属于app内部的页面,使用时已全部加载完毕,因此监控页面性能时不统计页面加载时长等信息,更多的是对页面内请求、资源请求和用户行为的监控;
由于微信官方和小程序代码的要求,集成方式对比 Web 端会相对严格一些。

小程序需要监控的数据

1.JavaScript异常监控:不论是 Web 端还是小程序端,对 JavaScript 异常的监控都是必要的;
2.页面内请求监控:对于小程序来说,需要统计发送网络请求的 swan.request() 异常时的请求状态、请求时长、请求地址等;
3.资源加载监控:当需要下载资源到本地的
4.swan.downloadFile() 出现异常时,统计加载时间、异常类型、资源地址等;
5.页面性能监控:访问监控、页面来源及流向监控等,方便更好的对小程序进行运营;
6.用户数据统计:用户的分布、操作系统及版本、app版本、IP 地址等,给错误的分析提供更多条件。

收集方式

  1. 简单收集

小程序App()生命周期里提供了onError函数,可以通过在onError里收集异常信息。

  1. 用户操作路径收集

一些较隐蔽的错误如果只有错误栈信息,排查起来会比较难,如果有用户操作的路径,在排查时就方便多了。

小程序性能监控

  • 白屏监控

用户在访问网页的时候,在浏览器开始显示之前都会有一个的白屏过程,在移动端,受限于设备性能和网络速度,白屏会更加明显。

  • 白屏时间

页面完全空白的时间,web可以在页面的head底部添加的JS代码用来做白屏时间的标记。

微信web资源离线存储

通过使用微信离线存储,Web开发者可借助微信提供的资源存储能力,直接从微信本地加载 Web
资源而不需要再从服务端拉取,从而减少网页加载时间,为微信用户提供更优质的网页浏览体验。每个公众号下所有 Web App 累计最多可缓存 5M
的资源。这个设计有点类似 HTML5 的 Application Cache。

八 前端异常上报方式

  • 影响因素

异常上报,可能产生影响的因素有:

上报的频率。当出现死循环,不断触发异常上报时,这个就跟 DDOS 攻击差不多了。
上报的数据量。不同的请求方式,能携带的数据量有限制。如果想要录制用户的操作,那么产生的数据量,不同情况下会不一样。
跨域。日志服务器有些是单独的。 不同 web 服务器对于请求的 body 大小限制不同。 上报请求的响应方式。响应同样会消耗资源。

  • 上报方式

通常来说,上报方式可以分两种:一种是通过 Img 方式上报,另一种是 AJAX 接口上报。

  1. Img上报

这种方式非常的简单快捷,也是最推荐的一种方式。该方式通过动态的创建一个 Img
标签的方式,向服务器请求资源,然后把上报信息通过查询参数的方式带到请求的后面。至于请求的图片资源,为了节省流量和响应速度,我们可以请求一个
1×1 的透明 GIF 图片。这样一来不会产生跨域的问题,而且不需要等待服务器返回的数据,只要进行上报即可,非常的简单。
但是,也有相应的弊端。由于 URL 的长度有限,所以携带的查询参数并不是无限的,因此需要筛选有用的信息进行上报,而不是无限的长度。

  1. AJAX 接口上报

由于这种方式本身也是一个请求,存在发生异常的情况。而且,通常来说上报接口的域名和业务列域名是不同的,因此还会存在跨域的问题需要处理。必须要等到接口响应后状态为
200 才能确定这次信息上报是成功的。
当然,AJAX 接口上报也有其自身的优点,那就可以携带的参数更大,而且还可以默认携带 Token 等信息。

总结
在上报参数长度可控的情况下还是更推荐通过 Img 方式进行监控信息上报。如果上报的数据量较大,不可控的情况下,使用 AJAX 接口上报更加保险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值