乐趣区

关于前端:前端监控之性能与异常

作者:京东批发 李菲菲

1 前言

现有的大部分监控计划都是针对服务端的,而针对前端的监控很少,诸如线上页面的白屏工夫是多少、动态资源的加载状况如何、接口申请耗时良久、什么时候挂掉了、为什么挂掉,这些都不分明。

同时,在产品推广过程中,常常须要统计页面的应用状况及用户行为,从而能够从经营和产品的角度去理解用户群体,进而迭代降级产品,使其更加贴近用户,为业务的扩大提供更多可能性。

因此,咱们须要一个前端的页面监控零碎,继续监控和预警页面性能的情况,并且在发现瓶颈时用于领导优化工作。

2 前端监控指标

前端监控次要蕴含两大块:性能监控及异样监控

  1. 保障稳定性(异样监控)
    谬误监控包含 JavaScript 代码谬误,Promsie 谬误,接口(XHR,fetch)谬误,资源加载谬误(script,link 等)等,这些谬误大多会导致页面性能异样甚至白屏。
  2. 晋升用户体验(性能监控)
    性能监控包含页面的加载工夫,接口响应工夫等,侧面反馈了用户体验的好坏。

3 性能监控

3.1 简略形容页面加载

简略看一下,从输出 url 到页面加载实现的过程如下:

首先须要通过 DNS(域名解析零碎)将 URL 解析为对应的 IP 地址,而后与这个 IP 地址确定的那台服务器建设起 TCP 网络连接,随后咱们向服务端抛出 HTTP 申请,服务端解决完咱们的申请之后,把指标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就能够开始走一个渲染的流程。渲染结束,页面便出现给了用户。

咱们能够将这个过程分为如下的过程片段:

  1. DNS 解析
  2. TCP 连贯
  3. HTTP 申请抛出
  4. 服务端解决申请,HTTP 响应返回
  5. 浏览器拿到响应数据,解析响应内容,把解析的后果展现给用户

3.2 从开发者角度,看页面加载各阶段

从输出 url 到用户能够应用页面的全过程工夫统计,会返回一个 PerformanceTiming 对象,单位均为毫秒。
对于 performace,曾经在《从前端角度浅谈性能》中进行过介绍,,上面再强调一下:

各阶段的性能耗时能够通过 API:window.performance 来获取,对应的具体方法有:performance.timing、performance.getEntriesByType(‘resource’)、performance.navigation 等。
如上,开发者能够通过 performance 中各阶段的工夫戳,别离获取到 页面各阶段的性能指标,具体的个动态资源的加载耗时、及 页面是否重定向和重定向耗时。

按触发顺序排列所有属性:

  • navigationStart: 在同一个浏览器上下文中,前一个网页(与以后页面不肯定同域)unload 的工夫戳,如果无前一个网页 unload,则与 fetchStart 值相等
  • redirectStart: 第一个 HTTP 重定向产生时的工夫。有跳转且是同域名内的重定向才算,否则值为 0
  • unloadEventStart: 前一个网页(与以后页面同域)unload 的工夫戳,如果无前一个网页 unload 或者前一个网页与以后页面不同域,则值为 0
  • redirectEnd: 最初一个 HTTP 重定向实现时的工夫。有跳转且是同域名内的重定向才算,否则值为 0
  • unloadEventEnd: 和 unloadEventStart 绝对应,返回前一个网页 unload 事件绑定的回调函数执行结束的工夫戳
  • fetchStart: 浏览器筹备好应用 HTTP 申请抓取文档的工夫,这产生在查看本地缓存之前
  • domainLookupStart:DNS 域名查问开始的工夫,如果应用了本地缓存(即无 DNS 查问)或长久连贯,则与 fetchStart 值相等
  • domainLookupEnd:DNS 域名查问实现的工夫,如果应用了本地缓存(即无 DNS 查问)或长久连贯,则与 fetchStart 值相等
  • connectStart:HTTP(TCP)开始建设连贯的工夫,如果是长久连贯,则与 fetchStart 值相等, 如果在传输层产生了谬误且从新建设连贯,则这里显示的是新建设的连贯开始的工夫
  • secureConnectionStart:HTTPS 连贯开始的工夫,如果不是平安连贯,则值为 0
  • connectEnd:HTTP(TCP)实现建设连贯的工夫(实现握手),如果是长久连贯,则与 fetchStart 值相等, 如果在传输层产生了谬误且从新建设连贯,则这里显示的是新建设的连贯实现的工夫
  • requestStart:HTTP 申请读取实在文档开始的工夫(实现建设连贯),包含从本地读取缓存, 连贯谬误重连时,这里显示的也是新建设连贯的工夫
  • responseStart:HTTP 开始接管响应的工夫(获取到第一个字节),包含从本地读取缓存
  • responseEnd:HTTP 响应全副接管实现的工夫(获取到最初一个字节),包含从本地读取缓存
  • domLoading: 开始解析渲染 DOM 树的工夫,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相干事件
  • domInteractive: 实现解析 DOM 树的工夫,Document.readyState 变为 interactive,并将抛出 readystatechange 相干事件
  • domContentLoadedEventStart:DOM 解析实现后,网页内资源加载开始的工夫, 文档产生 DOMContentLoaded 事件的工夫
  • domContentLoadedEventEnd:DOM 解析实现后,网页内资源加载实现的工夫(如 JS 脚本加载执行结束),文档的 DOMContentLoaded 事件的完结工夫
  • domComplete:DOM 树解析实现,且资源也准备就绪的工夫,Document.readyState 变为 complete,并将抛出 readystatechange 相干事件
  • loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的工夫, 如果没有绑定 load 事件,值为 0
  • loadEventEnd:load 事件的回调函数执行结束的工夫, 如果没有绑定 load 事件,值为 0

3.3 各阶段性能的计算(自定义)

1.  `const {timing, navigation} = window.performance`
1.  `const loadPageInfo = {};`
1.  ``
1.  `// 页面加载类型,辨别第一次 load 还是 reload, 0 首次加载、1 重加载 `
1.  `loadPageInfo.loadType = navigation.type;`
1.  ``
1.  `// 页面加载实现的工夫 - 简直代表了用户期待页面白屏的工夫 `
1.  `loadPageInfo.loadPage = timing.loadEventEnd - timing.navigationStart;`
1.  ``
1.  `// 重定向的工夫 `
1.  `loadPageInfo.redirect = timing.redirectEnd - timing.redirectStart;`
1.  ``
1.  `// 卸载页面的工夫 `
1.  `loadPageInfo.unloadEvent = timing.unloadEventEnd - timing.unloadEventStart;`
1.  ``
1.  `// 查问 DNS 本地缓存的工夫 `
1.  `loadPageInfo.appCache = timing.domainLookupStart - timing.fetchStart;`
1.  ``
1.  `//【重要】DNS 查问工夫 `
1.  `// 页面内是不是应用了太多不同的域名,导致域名查问的工夫太长?举荐 DNS 预加载。`
1.  `// 可应用 HTML5 Prefetch 预查问 DNS`
1.  `loadPageInfo.lookupDomain = timing.domainLookupEnd - timing.domainLookupStart;`
1.  ``
1.  `// HTTP(TCP)建设连贯实现握手的工夫 `
1.  `loadPageInfo.connect = timing.connectEnd - timing.connectStart;`
1.  ``
1.  `//【重要】HTTP 申请及获取 文档内容的工夫 `
1.  `loadPageInfo.request = timing.responseEnd - timing.responseStart;`
1.  ``
1.  `//【重要】前一个页面 unload 到 HTTP 获取到 页面第一个字节的工夫 `
1.  `//【起因】这能够了解为用户拿到你的资源占用的工夫,举荐 加异地机房,加 CDN 解决,加宽带,加 CPU 运算速度 `
1.  `// TTFB 即 Time To First Byte`
1.  `loadPageInfo.ttfb = timing.responseStart - timing.navigationStart;`
1.  ``
1.  `// 解析 DOM 树结构的工夫 `
1.  `loadPageInfo.domReady = timing.domComplete - timing.responseEnd;`
1.  ``
1.  `//【重要】执行 onload 回调函数的工夫 `
1.  `//【起因】是否太多不必要的操作都放在 onload 回调函数里执行了,举荐 提早加载、按需加载的策略 `
1.  `loadPageInfo.loadEvent = timing.loadEventEnd - timing.loadE`

4 异样监控

前端须要监控的谬误次要有两类:

  1. Javascript 谬误(js 谬误、promise 谬误)
  2. 监听 error 谬误(资源加载谬误)

4.1 console.error

1.  `// 重写 console.error,能够捕捉更全面的报错信息 `
1.  `var oldError = console.error;`
1.  ``
1.  ``
1.  `console.error = function(tempErrorMsg){`
1.  `var errorMsg = (arguments[0] && arguments[0].message ) || tempErrorMsg;`
1.  `var lineNumber = 0;`
1.  `var columnNumber = 0;`
1.  `var errorStack = arguments[0] && arguments[0].stack;`
1.  ``
1.  ``
1.  `if(!errorStack){`
1.  `saveJSError('console_error', errorMsg, '', lineNumber, columnNumber,'CustomizeError: ' + errorMsg);`
1.  `}else{`
1.  `saveJSError('console_error', errorMsg, '', lineNumber, columnNumber, errorStack);`
1.  `}`
1.  ``
1.  ``
1.  `return oldError.apply(console, arguments);`

4.2 error 事件

通过对 error 事件的监听,能够捕捉到 js 语法 及 资源加载 的谬误。依据 event.target.src / href 来判断是否为资源加载谬误。

1.  `window.addEventListener('error', function(e){`
1.  `var errorMsg = e.error && e.error.message,`
1.  `errorStack = e.error && e.error.stack,`
1.  `pageUrl = e.filename,`
1.  `lineNumber = e.lineno,`
1.  `columnNumber = e.colno;`
1.  ``
1.  ``
1.  `saveJSError('on_error', errorMsg, pageUrl, lineNumber, columnNumber, errorStack);`
1.  `} );`

4.3 Promise

 `// 捕捉未解决的 Promise 谬误 `
`window.onunhandledrejection = function(e){`
`var errorMsg = '';`
`var errorStack = '';`
`if(typeof e.reason === 'object'){`
`errorMsg = e.reason.message;`
`errorStack = e.reason.stack;`
`}else{`
`errorMsg = e.reason;`
`errorStack = '';`
 `}`
 `saveJSError('on_error', errorMsg, '', 0, 0,'UncaughtInPromiseError: ' + errorStack);`
  `}`

5 总结

以上,通过简略的 js 代码,即可实现对页面性能与异样的监控与数据上报,后续还须要相应具体的平台汇总,及相应的业务所需数据(如 PV、UV 等)的计算,能力真正实现对产品的页面数据出现,用于业务扩大及宣导。

6 后续

上述代码,实现了对页面性能及异样的监控,但其实前端的监控还包含了申请接口的监控与埋点的实现,后续将陆续推出,敬请期待。

退出移动版