背景

首先我们为什么要做前端系统呢,先看下面这张表,可以很显然的看出,前端的性能对于产品的价值提升还是蛮有帮助的,但是这些信息如果我们能实时的采集到,并且实施以监控,让整个产品在产品线上一直保持高效的运作,这才是我们的目的。

性能
收益
Google 延迟 400ms 搜索量下降 0.59%
Bing 延迟 2s 收入下降 4.3%
Yahoo 延迟 400ms 流量下降 5-9%
Mozilla 页面打开减少 2.2s 下载量提升 15.4%
Netflix 开启 Gzip 性能提升 13.25% 带宽减少50%

其次,也有利于我们发布的产品,能够及时发现我们的错误。如果一个产品在新的迭代中,发生不可描述的错误。

开始

以上是我们需要做的一些事情。

要做监控系统,首先我们得有一个对象。我们监控的对象!对象!对象!对象。

我在我的系统写了一个这样的页面,

<body> 
    <div>2</div> <div>2</div> <div>2</div> <div>2</div> <div>2</div> <div>2</div> </body>

没错这就是我们要监控的页面。这个.....真不是我懒。

然后接下来我一共设计了3块数据

  • 页面加载时间
  • 统计用户使用设备
  • 错误量的统计

页面加载时间

window.logInfo = {};   //统计页面加载时间
window.logInfo.openTime = performance.timing.navigationStart;
window.logInfo.whiteScreenTime = + new  Date() - window.logInfo.openTime;
document.addEventListener( 'DOMContentLoaded' , function  (event) {
   window.logInfo.readyTime = + new  Date() - window.logInfo.openTime;
});
window.onload =  function  () {
   window.logInfo.allloadTime = + new  Date() - window.logInfo.openTime;
   window.logInfo.nowTime =  new  Date().getTime();
   var  timname = {
     whiteScreenTime:  '白屏时间' ,
     readyTime:  '用户可操作时间' ,
     allloadTime:  '总下载时间' ,
     mobile:  '使用设备' ,
     nowTime:  '时间' ,
   };
   var  logStr =  '' ;
   for  ( var  in  timname) {
     console.warn(timname[i] +  ':'  + window.logInfo[i] +  'ms' );
     if  (i ===  'mobile' ) {
       logStr +=  '&'  + i +  '='  + window.logInfo[i];
     else  {
       logStr +=  '&'  + i +  '='  + window.logInfo[i];
     }
 
   }
   ( new  Image()).src =  '/action?'  + logStr;
};

统计用户使用设备

window.logInfo.mobile = mobileType();
function  mobileType() {
   var  u = navigator.userAgent, app = navigator.appVersion;
   var  type =  { // 移动终端浏览器版本信息
     ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),  //ios终端
     iPad: u.indexOf( 'iPad' ) > -1,  //是否iPad
     android: u.indexOf( 'Android' ) > -1 || u.indexOf( 'Linux' ) > -1,  //android终端或者uc浏览器
     iPhone: u.indexOf( 'iPhone' ) > -1 || u.indexOf( 'Mac' ) > -1,  //是否为iPhone或者QQHD浏览器
     trident: u.indexOf( 'Trident' ) > -1,  //IE内核
     presto: u.indexOf( 'Presto' ) > -1,  //opera内核
     webKit: u.indexOf( 'AppleWebKit' ) > -1,  //苹果、谷歌内核
     gecko: u.indexOf( 'Gecko' ) > -1 && u.indexOf( 'KHTML' ) == -1,  //火狐内核
     mobile: !!u.match(/AppleWebKit.*Mobile/i) || !!u.match(/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/),  //是否为移动终端
     webApp: u.indexOf( 'Safari' ) == -1  //是否web应该程序,没有头部与底部
   };
   var  lists = Object.keys(type);
   for ( var  i = 0; i < lists.length; i++) {
     if (type[lists[i]]) {
       return  lists[i];
     }
  
}

错误量的统计

window.onload =  function  () {
         window.logInfo.allloadTime = + new  Date() - window.logInfo.openTime;
         window.logInfo.nowTime =  new  Date().getTime();
         var  timname = {
             whiteScreenTime:  '白屏时间' ,
             readyTime:  '用户可操作时间' ,
             allloadTime:  '总下载时间' ,
             mobile:  '使用设备' ,
             nowTime:  '时间' ,
         };
         var  logStr =  '' ;
         for  ( var  in  timname) {
             console.warn(timname[i] +  ':'  + window.logInfo[i] +  'ms' );
             if  (i ===  'mobile' ) {
                 logStr +=  '&'  + i +  '='  + window.logInfo[i];
             else  {
                 logStr +=  '&'  + i +  '='  + window.logInfo[i];
             }
             
         }
         ( new  Image()).src =  '/action?'  + logStr;
     };
       
     var  defaults = {
         msg: '' ,   // 错误的具体信息
         url: '' ,   // 错误所在的url
         line: '' // 错误所在的行
         col: '' ,   // 错误所在的列
         nowTime:  '' , // 时间
     };
     window.onerror =  function (msg,url,line,col,error) {
         col = col || (window.event && window.event.errorCharacter) || 0;
 
         defaults.url = url;
         defaults.line = line;
         defaults.col =  col;
         defaults.nowTime =  new  Date().getTime();
 
         if  (error && error.stack){
             // 如果浏览器有堆栈信息,直接使用
             defaults.msg = error.stack.toString();
 
         } else  if  (arguments.callee){
             // 尝试通过callee拿堆栈信息
             var  ext = [];
             var  fn = arguments.callee.caller;
             var  floor = 3; 
             while  (fn && (--floor>0)) {
                 ext.push(fn.toString());
                 if  (fn  === fn.caller) {
                     break ;
                 }
                 fn = fn.caller;
             }
             ext = ext.join( "," );
             defaults.msg = error.stack.toString();
         }
         var  str =  ''
         for ( var  in  defaults) {
             // console.log(i,defaults[i]);
             if (defaults[i] ===  null  || defaults[i] === undefined) {
                 defaults[i] =  'null' ;
             }
             str +=  '&' + i +  '='  + defaults[i].toString();
         }
         srt = str.replace( '&' '' ).replace( '\n' , '' ).replace(/\s/g,  '' );
         ( new  Image()).src =  '/error?'  + srt;
     }

以上就是收集数据的全部,通过发送/action请求或者是/error请求,这些都是可以自定义的,我讲的只是整个过程是如何实现的。

然后通过我的的一个后台express.js把所有的请求处理并都记录下来,记录好后的数据是这样子的。

user_ip= 127.0 . 0.1 &whiteScreenTime= 185 &readyTime= 192 &allloadTime= 208 &mobile=webKit&nowTime= 1513071388941

数据处理

这里我是通过自己写的一段脚本进行解析,parse.js,这里不具体讲解,看源码即可。我展现下解析好的数据。

我以cvs的数据格式储存,因为后面图表的需要,我也支持json格式方式导出,只不过后面就需要你自己来配置可视化的界面了。

数据是这样的。

charts/csvData/2017-12-16time.csv

时间,白屏时间,用户可操作时间,总下载时间
1513427051482 , 137 , 137 , 153
1513427065080 , 470 , 471 , 507
1513427080040 , 127 , 127 , 143
1513428714345 , 274 , 275 , 323
1513428733583 , 267 , 268 , 317
1513428743167 , 268 , 268 , 317
1513428754796 , 276 , 276 , 328

数据展示

这里我用的是highcharts.js

具体的配置我不进行讲解,可以自己到官网进行查看。

下面是可视化的图表,显示的是每天各个时间段的信息。


环境

node >= 6.0.0

redis >= 2.6.0

在这里我说明下,因为如果这个部署在线上环境的时候,如果每次记录都进行记录的话,会消耗大量的内存,所以我架设了一层redis,为了防止大流量的冲击,然后可以每隔一段时间进行存储。

const express = require( 'express' );
const performance = require( './lib/performance.js' );
const app = express();
const router = express.Router();
router.get( '/' function  (req, res, next) {
   req.url =  './index.html' ;
   next();
});
app.use(router);
app.use(performance({
     time: 10,  // 秒为单位
     originalDir:  './originalData' // 数据的目录
     errorDir:  './errorData'  // 报错的目录
}))
app.use(express.static( './' ));
const server = app.listen(3000)

这里可以设置默认的时间,我这里以10秒为单位,为了demo的效果起见。一般我采用的是一分钟进行一次存储。

 


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

DOMContentLoaded与load的区别知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。