作者:京东零售 李菲菲
现有的大部分监控方案都是针对服务端的,而针对前端的监控很少,诸如线上页面的白屏时间是多少、静态资源的加载情况如何、接口请求耗时好久、什么时候挂掉了、为什么挂掉,这些都不清楚。
同时,在产品推广过程中,经常需要统计页面的使用情况及用户行为,从而可以从运营和产品的角度去了解用户群体,进而迭代升级产品,使其更加贴近用户,为业务的扩展提供更多可能性。
因而,我们需要一个前端的页面监控系统,持续监控和预警页面性能的状况,并且在发现瓶颈时用于指导优化工作。
前端监控主要包含两大块:性能监控及异常监控
简单看一下,从输入url到页面加载完成的过程如下:
首先需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户。
我们可以将这个过程分为如下的过程片段:
从输入url到用户可以使用页面的全过程时间统计,会返回一个PerformanceTiming对象,单位均为毫秒。
关于performace,已经在《从前端角度浅谈性能》中进行过介绍,,下面再强调一下:
各阶段的性能耗时可以通过API:window.performance来获取,对应的具体方法有:performance.timing、performance.getEntriesByType(‘resource’)、performance.navigation等。
如上,开发者可以通过performance中各阶段的时间戳,分别获取到 页面各阶段的性能指标,具体的个静态资源的加载耗时、及 页面是否重定向和重定向耗时。
按触发顺序排列所有属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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` |
前端需要监控的错误主要有两类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 );` |
通过对error事件的监听,可以捕捉到 js语法 及 资源加载 的错误。根据 event.target.src / href 来判断是否为资源加载错误。
1 2 3 4 5 6 7 8 9 10 | 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. `} );` |
1 2 3 4 5 6 7 8 9 10 11 12 13 | ` / / 捕获未处理的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 );` `}` |
以上,通过简单的js代码,即可实现对页面性能与异常的监控与数据上报,后续还需要相应具体的平台汇总,及相应的业务所需数据(如PV、UV等)的计算,才能真正实现对产品的页面数据呈现,用于业务扩展及宣导。
上述代码,实现了对页面性能及异常的监控,但其实前端的监控还包括了请求接口的监控与埋点的实现,后续将陆续推出,敬请期待。