关于javascript:前端监控平台系列JS-SDK已开源

42次阅读

共计 15741 个字符,预计需要花费 40 分钟才能阅读完成。

本文作者:cjinhuo,未经受权禁止转载。

<h1 style=”padding: 0px; font-weight: bold; color: black; font-size: 24px; text-align: center; line-height: 60px; margin-top: 10px; margin-bottom: 10px;”>
<span style=”color: #2db7f5; border-bottom: 2px solid #2db7f5;” class=”content”> 背景 </span>
</h1>

传统形式下一个前端我的项目发到正式环境后,所有报错信息只能通过用户应用时截图、口头形容发送到开发者,而后开发者来依据用户所形容的场景去模仿这个谬误的产生,这效率必定超级低,所以很多开源或免费的前端监控平台就应运而生,比方:

  • sentry
  • webfunny
  • fundebug

等等一些优良的监控平台

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block; padding-left: 10px;” class=”content”> 国内罕用的监控平台 </span><span style=”display: none;” class=”suffix”></span></h2>

sentry:从监控谬误、谬误统计图表、多重标签过滤和标签统计到触发告警,这一整套都很欠缺,团队我的项目须要充钱,而且数据量越大钱越贵

fundebug:除了监控谬误,还能够录屏,也就是记录谬误产生的前几秒用户的所有操作,压缩后的体积只有几十 KB,但操作稍微繁琐

webfunny:也是含有监控谬误的性能,能够反对千万级别日 PV 量,额定的亮点是能够近程调试、性能剖析,也能够 docker 私有化部署(收费),业务代码加密过

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block; padding-left: 10px;” class=”content”> 为什么不抉择下面三个监控平台或者其余监控平台,为什么要本人搞?</span><span style=”display: none;” class=”suffix”></span></h2>

  1. 首先 sentryfundebug须要投入大量金钱来作为反对,而 webfunny 虽是能够用 docker 私有化部署,但因为其代码没有开源,二次开发受限
  2. 本人开发能够将公司所有的 SDK 对立成一个,包含但不限于:埋点平台 SDK、性能监控 SDK

<h1 style=”padding: 0px; font-weight: bold; color: black; font-size: 24px; text-align: center; line-height: 60px; margin-top: 10px; margin-bottom: 10px;”>
<span style=”color: #2db7f5; border-bottom: 2px solid #2db7f5;” class=”content”> 监控平台的组成 </span>
</h1>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 整体流程 </span><span style=”display: none;” class=”suffix”></span></h2>

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”> 整体流程 </span>
</div>

从上图能够看进去,如果须要自研监控平台须要做三个局部:

  1. APP 监控 SDK:收集错误信息并上报
  2. server 端:接管错误信息,解决数据并做长久化,而后依据告警规定告诉对应的开发人员
  3. 可视化平台:从数据存储引擎拿出相干错误信息进行渲染,用于疾速定位问题

<h1 style=”padding: 0px; font-weight: bold; color: black; font-size: 24px; text-align: center; line-height: 60px; margin-top: 10px; margin-bottom: 10px;”>
<span style=”font-size: 24px; color: #2db7f5; border-bottom: 2px solid #2db7f5;” class=”content”> 监控 SDK</span>
</h1>
<h2 style=”margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 20px; color: #2db7f5; display: inline-block;” class=”content”> 整体代码架构 </span><span style=”display: none;” class=”suffix”></span></h2>

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”> 代码架构 </span>
</div>

整体代码架构应用 公布 - 订阅 设计模式以便后续迭代性能,解决逻辑根本都在 HandleEvents 文件中, 这样设计的益处是如果想交叉 hook 或者迭代性能能够在处理事件回调多增加一个函数

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>HandleEvents</span>
</div>

<h2 style=”margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 20px; color: #2db7f5; display: inline-block; padding-left: 10px;” class=”content”>web 错误信息收集 </span><span style=”display: none;” class=”suffix”></span></h2>

个别状况下都是通过重写 js 原生事件而后拿到错误信息,比方 ajax 申请,通过重写xhrfetch 事件来截取接口信息,所以咱们须要优先编写一个易于重写事件的函数来复用。

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>replaceOld</span>
</div>

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”> 接口谬误 </span><span style=”display: none;” class=”suffix”></span></h3>

所有的申请第三方库都是基于 xhrfetch 二次封装的,所以只须要重写这两个事件就能够拿到所有的接口申请的信息,通过判断 status 的值来判断以后接口是否是失常的。举个例子,重写 xhr 的代码操作:

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>Xhr 重写 </span>
</div>

下面除了拿去接口的信息之外还做一个操作:如果是 SDK 发送的接口,就不必收集该接口的信息。如果须要公布事件就调用 triggerHandlers(EVENTTYPES.XHR, this.mito_xhr),相似的,fetch 也是用这种形式来重写。

对于接口跨域、超时的问题 :这两种状况产生的时候,接口返回的响应体和响应头外面都是空的,status 等于 0,所以很难辨别两者,然而失常状况下,个别我的项目中都的申请都是简单申请,所以在正式申请会先进行 option 进行预申请,如果是跨域的话根本几十毫秒就会返回来,所以以此作为临界值来判断跨域与超时的问题(如果是接口不存在也会被判断成接口跨域)。

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>js 代码谬误 && 资源谬误 </span><span style=”display: none;” class=”suffix”></span></h3>

监听 windowerror事件

window.addEventListener('error',function(e){// 拿到错误信息,公布事件:triggerHandlers}, true)
  • 资源谬误

判断 e.target.localName 是否有值,有的话就是资源谬误,在 handleErrors 中拿到信息:

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>handleError</span>
</div>

  • 代码谬误

下面判断为 false 时,代表是代码谬误,在回调中能够拿到对应的错误代码文件、代码行数等等信息,而后通过 source-map 这个 npm 包+sourceMap 文件进行解析,就能够还原出线上实在代码谬误的地位。

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”> 监听 unhandledrejection</span><span style=”display: none;” class=”suffix”></span></h3>

Promisereject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>unhandledrejection 监听 </span>
</div>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 用户行为信息收集 </span><span style=”display: none;” class=”suffix”></span></h2>

单纯收集错误信息是能够进步谬误定位的效率,但如果再配合上用户行为的话就精益求精,定位谬误的效率再上一层,如下图所示,能够清晰的看到用户做了哪些事:进了哪个页面 => 点击了哪个按钮 => 触发了哪个接口:

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”> 用户行为前端页面展现 </span>
</div>

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>dom 事件信息 </span><span style=”display: none;” class=”suffix”></span></h3>

dom事件获取包含很多:clickinputdoubleClick等等,一种间接在 window 下面监听 click 事件(留神第三个参数为true):

window.addEventListener('click',function(e){
    // 利用节流,以防事件触发过快
  // 公布事件 triggerHandlers
}, true)

还有一种是通过重写 window.addEventListener 的形式来截取开发者对 dom 的监听事件。

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”> 路由切换信息 </span><span style=”display: none;” class=”suffix”></span></h3>

在单页利用中有两种路由变换:hashchangehistory

  • history

当浏览器反对 history 模式时,会被以下两个事件所影响:pushStatereplaceState,且这两个事件不会触发 onpopstate 的回调,所以咱们须要监听这个三个事件:

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>onpopstate 重写 </span>
</div>

  • hashchange

当浏览器只反对 hashchange 时,就须要重写 hashchange:

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>hashchange 重写 </span>
</div>

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;” data-id=”heading-7″><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>console 信息 </span><span style=”display: none;” class=”suffix”></span></h3>

失常状况下正式环境是不应该有 console 的,那为什么要收集 console 的信息?第一:非正常状况下,正式环境或预发环境也可能会有 console,第二:很多时候也能够把sdk 放入测试环境下面调试。所以最终还是决定收集 console 信息,然而在初始化的时候的传参来通知 sdk 是否监听 console 的信息收集。

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>console 重写 </span>
</div>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 框架层错误信息收集 </span><span style=”display: none;” class=”suffix”></span></h2>

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>Vue</span><span style=”display: none;” class=”suffix”></span></h3>

vue2.6官网提供了两个报错函数的回调:Vue.config.errorHandlerVue.config.warnHandler

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>vue 错误信息收集 </span>
</div>

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>React</span><span style=”display: none;” class=”suffix”></span></h3>

React16.13 中提供了 componentDidCatch 钩子函数来回调错误信息,所以咱们能够新建一个类 ErrorBoundary 来继承 React,而后而后申明 componentDidCatch 钩子函数,能够拿到错误信息(目前没写 react 的谬误收集,看官网文档简述,简易版应该是这样写的)。

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”>react 错误信息收集 </span>
</div>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 自定义上报谬误 </span><span style=”display: none;” class=”suffix”></span></h2>

下面收集的是 web 端的代码谬误、接口报错和框架层面的报错等等,还有一种是业务错误信息:比方点击领取的时候,可能服务端接口返回 200,然而响应体是错误信息,就须要手动上报这块的错误信息。既然要手动上报,SDK就须要提供一个全局函数性能开发者调用:

import MITO from 'mitojs'
MITO.log({
  info: '领取失败,余额有余',
  tag: 'business'
})

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”>Breadcrumb 收集 </span><span style=”display: none;” class=”suffix”></span></h2>

在下面收集完错误信息的时候,都在最初追加一行 breadcrumb.push(data),这样就能够保留用户的行为轨迹,默认状况设置20 长度,也能够在初始化时可配置,然而倡议最高不要超过100,因为如果信息过多,内存占用过大,对页面不太敌对。

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”> 类型整合 </span><span style=”display: none;” class=”suffix”></span></h3>

在每个事件类型的回调的时候都将类型整合:比方用户点击、路由跳转都是属于用户行为,这样做的起因是让开发者更好过滤无用信息和精准定位到须要的信息。

<div style=”padding: 0px; font-weight: bold; color: black; font-size: 14px; text-align: center; line-height: 30px; margin-bottom: 10px;”>
<span style=”color: #2db7f5;” class=”content”> 用户行为类型整合 </span>
</div>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”>Error id 生成 </span><span style=”display: none;” class=”suffix”></span></h2>

每个谬误事件触发时都会有很多信息,咱们须要尽量保障每个不同信息的谬误生成的 id 不一样,这边采取的措施是先将每个谬误的对象 key 依照肯定规定递归排序,而后依据每个对象的值进行hashCode,失去一串errorId

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 上报错误信息 </span><span style=”display: none;” class=”suffix”></span></h2>

当 SDK 拿到谬误的所有信息时须要上报到服务端,有几种形式上报服务端

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”> 通过 xhr 上报 </span><span style=”display: none;” class=”suffix”></span></h3>

通过 xhr 上报,如果设置成异步的时候,当用户跳转新页面或者敞开页面时就会失落以后这个申请,如果设置成同步,又会让页面造成卡顿的景象

sentry目前是通过 xhr 发送的,不过它在发送前会推到它设置的一个申请缓冲区 _buffer,以此来优化并发申请过多的问题。

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>Image 的模式来发送申请 </span><span style=”display: none;” class=”suffix”></span></h3>

特点:

  1. 没有跨域问题、
  2. 发 GET 申请之后不须要获取和解决数据、
  3. 服务器也不须要发送数据、
  4. 不会携带以后域名 cookie、不会阻塞页面加载,影响用户的体验,只需 new Image 对象、
  5. 相比于 BMP/PNG 体积最小,能够节约 41% / 35% 的网络资源小

<h3 style=”margin-top: 20px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”font-size: 16px; color: #2db7f5; display: inline-block; padding-left: 10px; border-left: 4px solid #2db7f5;” class=”content”>Navigator.sendBeacon</span><span style=”display: none;” class=”suffix”></span></h3>

MDN:可用于通过 HTTP 将大量数据异步传输到 Web 服务器,统计和诊断代码通常要在 unload 或者 beforeunload 事件处理器中发动一个同步 XMLHttpRequest 来发送数据。同步的 XMLHttpRequest 迫使用户代理提早卸载文档,并使得下一个导航呈现的更晚。下一个页面对于这种较差的载入体现无能为力

特点:

  1. 收回的是异步申请,并且是 POST 申请
  2. 收回的申请,是放到的浏览器工作队列执行的,脱离了以后页面,所以不会阻塞以后页面的卸载和前面页面的加载过程,用户体验较好
  3. 只能判断出是否放入浏览器工作队列,不能判断是否发送胜利
  4. Beacon API不提供相应的回调,因而后端返回最好省略response body
  5. 兼容性不是很敌对

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 用户惟一标识 </span><span style=”display: none;” class=”suffix”></span></h2>

为了不便统计用户量,在每次上报的时候会带一个惟一标识符 trackerId,生成这个trackerId 的路径有两种:

  1. 如果你是用 ajax 上报的话,发现 cookie 中没有带 trackerId 这个字段,服务端生成并 setCookie 设置到用户端的cookie
  2. 间接用 SDK 生成,在每次上报之前都判断 localstorage 是否存在trackerId,有则随着错误信息一起发送,没有的话生成一个并设置到localstorage

<h1 style=”padding: 0px; font-weight: bold; color: black; font-size: 24px; text-align: center; line-height: 60px; margin-top: 10px; margin-bottom: 10px;”>
<span style=”color: #2db7f5; border-bottom: 2px solid #2db7f5;” class=”content”> 总结 </span>
</h1>

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”>SDK 小结 </span><span style=”display: none;” class=”suffix”></span></h2>

订阅事件 => 重写原生事件 => 触发原生事件(公布事件)=> 拿到错误信息 => 提取有用的错误信息 => 上报服务端

<h2 style=”margin-top: 25px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;”><span style=”display: none;” class=”prefix”></span><span style=”color: #2db7f5; display: inline-block;” class=”content”> 对于开源 </span><span style=”display: none;” class=”suffix”></span></h2>

SDK 开源:mitojs,下一篇会讲服务端的表结构设计思路、怎么在 千万 条数据中多重标签 毫秒 级查问谬误事件以及更好的告警机制告诉开发人员

感兴趣的小伙伴能够点个关注,后续好文一直!!!

正文完
 0