运用Node.js + MongoDB实现一个简单的日志分析系统

使用Node.js + MongoDB实现一个简单的日志分析系统

 

使用Node.js + MongoDB实现一个简单的日志分析系统

分类: NoSQL MongoDB JavaScript Web开发 4937人阅读 评论(5) 收藏 举报


        在最近的项目中,为了便于分析把项目的日志都存成了JSON格式。之前日志直接存在了文件中,而MongoDB适时闯入了我的视线,于是就把log存进了MongoDB中。log只存起来是没有意义的,最关键的是要从日志中发现业务的趋势、系统的性能漏洞等。之前有一个用Java写的分析模块,运行在Tomcat下。实现相当的重量级,添加一个新指标的流程也比较繁琐,而且由于NFS的原因还导致分析失败。一直想改写,最初想用Ruby On Rails,可是一直没有时间学习和开发(在找借口啊!)。在杭州QCon 2011上又遇到了Node.js,虽然之前也听说过,但是没有深入研究,听了淘宝苏千 的演讲后,当时了就有要用Node.js实现这个日志分析系统的想法。前端用JS,服务器用JS,就连数据库的Shell都是JS,想想就够酷的——当然最关键是代码量小。

        一、用Node.js实现服务器端代码

        为了有良好的风格和快速的代码编写,不可避免地应该采用一个简单的框架。Express实现了大部分的功能,可是好需要花一定时间熟悉,并且看起来对这个项目来说有些重量级。在Node.js的官网上有一个聊天的Demo,这个代码简单移动,封装了对URL的处理和返回JSON。于是我就直接使用了fu.js,重写了server.js:

 

[javascript] view plaincopyprint?
  1. HOST = null// localhost  
  2. PORT = 8001;  
  3.   
  4. var fu = require("./fu"),  
  5.     sys = require("util"),  
  6.     url = require("url"),  
  7.     mongo = require("./request_handler");  
  8.   
  9. fu.listen(Number(process.env.PORT || PORT), HOST);  
  10.   
  11. fu.get("/", fu.staticHandler("index.html"));  

         太简单了吧?!不过的确是这样,一个服务器已经建立起来了。

 

         下面看处理请求的request_handler.js代码:

 

[javascript] view plaincopyprint?
  1. var mongodb = require("mongodb");  
  2. var fu = require("./fu");  
  3.   
  4.   
  5. // TOP 10 user Action  
  6. fu.get("/userActionTop10"function(req, res){  
  7.   mongodb.connect('mongodb://localhost:27017/log'function(err, conn){  
  8.     conn.collection('action_count'function(err, coll){  
  9.       coll.find({"value.action":{$in:user_action}}).sort({"value.count":-1}).limit(10).toArray(function(err, docs){  
  10.         if(!err){  
  11.           var action = [];  
  12.           var count = [];  
  13.           for(var i = 0; i < docs.length; i ++){  
  14.             //console.log(docs[i]);  
  15.             action.push(docs[i].value.action);  
  16.             count.push(docs[i].value.count);  
  17.           }  
  18.           res.simpleJSON(200, {action:action, count:count});  
  19.            
  20.           // 一定要记得关闭数据库连接  
  21.           conn.close();  
  22.         }  
  23.       });  
  24.     });  
  25.   });  
  26. });  

 

          同样很简单。

 

          二、客户端

          日志系统的最重要的是可视化显示,这里使用了JQuery的一个插件jqPlot Chart。首先使用一个静态的HTML页面,用来作为图形显示的容器:

 

[html] view plaincopyprint?
  1. <!DOCTYPE html>  
  2. <html>  
  3.   <head>  
  4.     <meta charset="utf-8">  
  5.     <title>Rendezvous Monitor System</title>  
  6.     <!--[if lt IE 9]><script src="js/excanvas.js"><![endif]-->  
  7.     <script src="js/jquery.min.js"></script>  
  8.     <script src="js/jquery.jqplot.min.js"></script>  
  9.     <script src="js/plugins/jqplot.barRenderer.min.js"></script>  
  10.     <script src="js/plugins/jqplot.categoryAxisRenderer.min.js"></script>  
  11.     <script src="js/plugins/jqplot.canvasTextRenderer.min.js"></script>  
  12.     <script src="js/plugins/jqplot.canvasAxisTickRenderer.min.js"></script>  
  13.     <script src="js/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>  
  14.     <script src="js/plugins/jqplot.pointLabels.min.js"></script>  
  15.     <script src="js/plugins/jqplot.dateAxisRenderer.min.js"></script>  
  16.     <script src="js/plugins/jqplot.json2.min.js"></script>  
  17.     <link rel="stylesheet" href="js/jquery.jqplot.min.css">  
  18.     <link rel="stylesheet" href="style/base.css">  
  19.     <script src="js/charts.js"></script>  
  20.   </head>  
  21.   <body>  
  22.   </body>  
  23. </html>  

          几乎是jqPlot的示例中的完整拷贝,好吧,我承认我太懒了。

 

          下面是看用来显示生成图形的chart.js:

 

[javascript] view plaincopyprint?
  1. // Store all chart drawing function, if we want to disable one chart, only need  
  2. // comment the push line when putting fucntion into the array.  
  3. var draws = [];  
  4.   
  5. /****************************** TOP 10 User Action Start *********************************/  
  6. document.write('<div id="userActionTop10Chart"></div>');  
  7.   
  8.   
  9. var drawUserActionTop10Chart = function(){  
  10.   if(!$("#userActionTop10Chart").attr('class')){  
  11.     $("#userActionTop10Chart").attr('class''small_chart');  
  12.   }  
  13.   
  14.   
  15.   $.ajax({  
  16.     async:false,  
  17.     url: '/userActionTop10',  
  18.     dataType:'json',  
  19.     cache: false,  
  20.     success:function(data){  
  21.       try{  
  22.         $('#userActionTop10Chart').html('');  
  23.   
  24.   
  25.         $.jqplot('userActionTop10Chart', [data.count], {  
  26.           title: "TOP 10 User Action",  
  27.           seriesDefaults:{  
  28.             renderer:$.jqplot.BarRenderer,  
  29.             rendererOptions: {fillToZero: true},  
  30.             pointLabels: {  
  31.               show:true,  
  32.               ypadding:1  
  33.             }  
  34.           },  
  35.           axesDefaults:{  
  36.             tickRenderer:$.jqplot.CanvasAxisTickRenderer,  
  37.             tickOptions: {  
  38.               angle: -30,  
  39.               fontSize: '12px'  
  40.             }  
  41.           },  
  42.           axes: {  
  43.             xaxis: {  
  44.               renderer: $.jqplot.CategoryAxisRenderer,  
  45.               ticks: data.action  
  46.             },  
  47.             yaxis: {  
  48.               pad: 1.05  
  49.             }  
  50.           }  
  51.         });  
  52.       }catch(e){  
  53.         //alert(e.message);  
  54.       }  
  55.     }  
  56.   });  
  57. }  
  58.   
  59.   
  60. draws.push('drawUserActionTop10Chart');  
  61.   
  62.   
  63. /******************************* TOP 10 User Action End ************************************/  
  64.   
  65. /*********** Chart Start *****************/  
  66.   
  67.   
  68. //Put your chart drawing function here  
  69. //1. insert a div for the chart  
  70. //2. implement the function drawing chart  
  71. //3. push the function name into the array draws  
  72.   
  73.   
  74. /*********** Chart End *******************/  
  75.   
  76.   
  77.   
  78. // Draw all charts  
  79. var drawAllCharts = function(){  
  80.   for(var i = 0; i < draws.length; i ++){  
  81.     eval(draws[i] + "()");  
  82.   }  
  83.   
  84.   
  85.  //Recall itself in 5 minute.  
  86.  window.setTimeout(drawAllCharts, 5 * 60 * 1000);  
  87. }  
  88.   
  89.   
  90. //  
  91. $(function(){  
  92.   drawAllCharts();  
  93. });  

 

 

        服务器端和客户端的代码都有了,那就跑起来看效果吧:

运用Node.js + MongoDB实现一个简单的日志分析系统

         好像忘了什么?日志的分析代码。

 

          三、使用MongoDB 增量式MapReduce实现日志分析

          在MongoDB的文档中有关于Incremental MapReduce的介绍。刚开始一直以为MongoDB实现Streaming处理,可以自动执行增量式的MapReduce。最后发现原来是我理解有误,文档里并没有写这一点,只是说明了如何设置才能增量执行MapReduce。

         为了方便,我把MapReduce使用MongoDB的JavaScript写在了单独的js文件中,然后通过crontab定时执行。stats.js的代码:

 

[javascript] view plaincopyprint?
  1. /************** The file is executed per 5 minutes by /etc/crontab.*****************/  
  2. var action_count_map = function(){  
  3.   emit(this.action, {action:this.action, count:1});  
  4. }  
  5.   
  6. var action_count_reduce = function(key, values){  
  7.   var count = 0;  
  8.   values.forEach(function(value){  
  9.     count += value.count;  
  10.   });  
  11.   return {action:key, count : count};  
  12. }  
  13.   
  14.   
  15. db.log.mapReduce(action_count_map, action_count_reduce, {query : {'action_count' : {$ne:1}},out: {reduce:'action_count'}});  
  16.   
  17. db.log.update({'action_count':{$ne:1}}, {$set:{'action_count':1}}, falsetrue);  

       思路很简单:

 

       1. 在map中将每个action访问次数设为1

       2. reduce中,统计相同action的访问次数

       3. 执行mapReduce。指定了查询为‘action_count’不等于1,也就是没有执行过该统计;将结果存储在‘action_count’集合,并且使用reduce选项表示该结果集作为下次reduce的输入。

       4. 在当前所有日志记录设置'action_count'的值为1,表示已经执行过该统计。不知道这种是否会造成没有还没有统计过的记录也被更新??望有经验的大侠赐教!

 

       定时执行stats.js的shell: