Node.js和MongoDB實(shí)現(xiàn)簡(jiǎn)單日志分析系統(tǒng)
在最近的項(xiàng)目中,為了便于分析把項(xiàng)目的日志都存成了JSON格式。之前日志直接存在了文件中,而MongoDB適時(shí)闖入了我的視線,于是就把log存進(jìn)了MongoDB中。log只存起來(lái)是沒有意義的,最關(guān)鍵的是要從日志中發(fā)現(xiàn)業(yè)務(wù)的趨勢(shì)、系統(tǒng)的性能漏洞等。之前有一個(gè)用Java寫的分析模塊,運(yùn)行在Tomcat下。實(shí)現(xiàn)相當(dāng)?shù)闹亓考?jí),添加一個(gè)新指標(biāo)的流程也比較繁瑣,而且由于NFS的原因還導(dǎo)致分析失敗。一直想改寫,最初想用Ruby On Rails,可是一直沒有時(shí)間學(xué)習(xí)和開發(fā)(在找借口?。。T诤贾軶Con 2011上又遇到了Node.js,雖然之前也聽說(shuō)過(guò),但是沒有深入研究,聽了淘寶蘇千 的演講后,當(dāng)時(shí)了就有要用Node.js實(shí)現(xiàn)這個(gè)日志分析系統(tǒng)的想法。前端用JS,服務(wù)器用JS,就連數(shù)據(jù)庫(kù)的Shell都是JS,想想就夠酷的——當(dāng)然最關(guān)鍵是代碼量小。
一、用Node.js實(shí)現(xiàn)服務(wù)器端代碼
為了有良好的風(fēng)格和快速的代碼編寫,不可避免地應(yīng)該采用一個(gè)簡(jiǎn)單的框架。Express實(shí)現(xiàn)了大部分的功能,可是好需要花一定時(shí)間熟悉,并且看起來(lái)對(duì)這個(gè)項(xiàng)目來(lái)說(shuō)有些重量級(jí)。在Node.js的官網(wǎng)上有一個(gè)聊天的Demo ,這個(gè)代碼簡(jiǎn)單移動(dòng),封裝了對(duì)URL的處理和返回JSON。于是我就直接使用了fu.js,重寫了server.js:
HOST = null; // localhost
PORT = 8001;
var fu = require("./fu"),
sys = require("util"),
url = require("url"),
mongo = require("./request_handler");
fu.listen(Number(process.env.PORT || PORT), HOST);
fu.get("/", fu.staticHandler("index.html"));
太簡(jiǎn)單了吧?!不過(guò)的確是這樣,一個(gè)服務(wù)器已經(jīng)建立起來(lái)了。
下面看處理請(qǐng)求的request_handler.js代碼:
var mongodb = require("mongodb");
var fu = require("./fu");
// TOP 10 user Action
fu.get("/userActionTop10", function(req, res){
mongodb.connect('mongodb://localhost:27017/log', function(err, conn){
conn.collection('action_count', function(err, coll){
coll.find({"value.action":{$in:user_action}}).sort({"value.count":-1}).limit(10).toArray(function(err, docs){
if(!err){
var action = [];
var count = [];
for(var i = 0; i < docs.length; i ++){
//console.log(docs[i]);
action.push(docs[i].value.action);
count.push(docs[i].value.count);
}
res.simpleJSON(200, {action:action, count:count});
// 一定要記得關(guān)閉數(shù)據(jù)庫(kù)連接
conn.close();
}
});
});
});
});
二、客戶端
日志系統(tǒng)的最重要的是可視化顯示,這里使用了JQuery的一個(gè)插件jqPlot Chart 。首先使用一個(gè)靜態(tài)的HTML頁(yè)面,用來(lái)作為圖形顯示的容器:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Rendezvous Monitor System</title>
<!--[if lt IE 9]><script src="js/excanvas.js"><![endif]-->
<script src="js/jquery.min.js"></script>
<script src="js/jquery.jqplot.min.js"></script>
<script src="js/plugins/jqplot.barRenderer.min.js"></script>
<script src="js/plugins/jqplot.categoryAxisRenderer.min.js"></script>
<script src="js/plugins/jqplot.canvasTextRenderer.min.js"></script>
<script src="js/plugins/jqplot.canvasAxisTickRenderer.min.js"></script>
<script src="js/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>
<script src="js/plugins/jqplot.pointLabels.min.js"></script>
<script src="js/plugins/jqplot.dateAxisRenderer.min.js"></script>
<script src="js/plugins/jqplot.json2.min.js"></script>
<link rel="stylesheet" href="js/jquery.jqplot.min.css">
<link rel="stylesheet" href="style/base.css">
<script src="js/charts.js"></script>
</head>
<body>
</body>
</html>
幾乎是jqPlot的示例中的完整拷貝,好吧,我承認(rèn)我太懶了。
下面是看用來(lái)顯示生成圖形的chart.js:
// Store all chart drawing function, if we want to disable one chart, only need
// comment the push line when putting fucntion into the array.
var draws = [];
/****************************** TOP 10 User Action Start *********************************/
document.write('<div id="userActionTop10Chart"></div>');
var drawUserActionTop10Chart = function(){
if(!$("#userActionTop10Chart").attr('class')){
$("#userActionTop10Chart").attr('class', 'small_chart');
}
$.ajax({
async:false,
url: '/userActionTop10',
dataType:'json',
cache: false,
success:function(data){
try{
$('#userActionTop10Chart').html('');
$.jqplot('userActionTop10Chart', [data.count], {
title: "TOP 10 User Action",
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
rendererOptions: {fillToZero: true},
pointLabels: {
show:true,
ypadding:1
}
},
axesDefaults:{
tickRenderer:$.jqplot.CanvasAxisTickRenderer,
tickOptions: {
angle: -30,
fontSize: '12px'
}
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: data.action
},
yaxis: {
pad: 1.05
}
}
});
}catch(e){
//alert(e.message);
}
}
});
}
draws.push('drawUserActionTop10Chart');
/******************************* TOP 10 User Action End ************************************/
/*********** Chart Start *****************/
//Put your chart drawing function here
//1. insert a div for the chart
//2. implement the function drawing chart
//3. push the function name into the array draws
/*********** Chart End *******************/
// Draw all charts
var drawAllCharts = function(){
for(var i = 0; i < draws.length; i ++){
eval(draws[i] + "()");
}
//Recall itself in 5 minute.
window.setTimeout(drawAllCharts, 5 * 60 * 1000);
}
//
$(function(){
drawAllCharts();
});
服務(wù)器端和客戶端的代碼都有了,那就跑起來(lái)看效果吧:
好像忘了什么?日志的分析代碼。
三、使用MongoDB 增量式MapReduce實(shí)現(xiàn)日志分析
在MongoDB的文檔中有關(guān)于Incremental MapReduce的介紹。剛開始一直以為MongoDB實(shí)現(xiàn)Streaming處理,可以自動(dòng)執(zhí)行增量式的MapReduce。最后發(fā)現(xiàn)原來(lái)是我理解有誤,文檔里并沒有寫這一點(diǎn),只是說(shuō)明了如何設(shè)置才能增量執(zhí)行MapReduce。
為了方便,我把MapReduce使用MongoDB的JavaScript寫在了單獨(dú)的js文件中,然后通過(guò)crontab定時(shí)執(zhí)行。stats.js的代碼:
/************** The file is executed per 5 minutes by /etc/crontab.*****************/
var action_count_map = function(){
emit(this.action, {action:this.action, count:1});
}
var action_count_reduce = function(key, values){
var count = 0;
values.forEach(function(value){
count += value.count;
});
return {action:key, count : count};
}
db.log.mapReduce(action_count_map, action_count_reduce, {query : {'action_count' : {$ne:1}},out: {reduce:'action_count'}});
db.log.update({'action_count':{$ne:1}}, {$set:{'action_count':1}}, false, true);
思路很簡(jiǎn)單:
1. 在map中將每個(gè)action訪問(wèn)次數(shù)設(shè)為1
2. reduce中,統(tǒng)計(jì)相同action的訪問(wèn)次數(shù)
3. 執(zhí)行mapReduce。指定了查詢?yōu)椤產(chǎn)ction_count'不等于1,也就是沒有執(zhí)行過(guò)該統(tǒng)計(jì);將結(jié)果存儲(chǔ)在‘a(chǎn)ction_count'集合,并且使用reduce選項(xiàng)表示該結(jié)果集作為下次reduce的輸入。
4. 在當(dāng)前所有日志記錄設(shè)置'action_count'的值為1,表示已經(jīng)執(zhí)行過(guò)該統(tǒng)計(jì)。不知道這種是否會(huì)造成沒有還沒有統(tǒng)計(jì)過(guò)的記錄也被更新??望有經(jīng)驗(yàn)的大俠賜教!
定時(shí)執(zhí)行stats.js的shell:
*/5 * * * * root cd /root/log; mongo localhost:27017/log stats.js
好了,這就是全部的代碼,沒有什么特別玄妙的地方,不過(guò)Node.js真的是個(gè)好東西。
- Docker mongoDB 4.2.1 安裝并收集springboot日志的步驟詳解
- MongoDB中4種日志的詳細(xì)介紹
- 如何利用MongoDB存儲(chǔ)Docker日志詳解
- MongoDB數(shù)據(jù)庫(kù)的日志文件深入分析
- SpringBoot中l(wèi)ogback日志保存到mongoDB的方法
- Linux下定時(shí)切割Mongodb數(shù)據(jù)庫(kù)日志并刪除指定天數(shù)前的日志記錄
- 深入講解MongoDB的慢日志查詢(profile)
- 使用MongoDB分析Nginx日志的方法詳解
- MongoDB運(yùn)行日志實(shí)現(xiàn)自動(dòng)分割的方法實(shí)例
- MongoDB日志文件過(guò)大的解決方法
- 詳解MongoDB中的日志模塊
相關(guān)文章
NodeJS?基于?Dapr?構(gòu)建云原生微服務(wù)應(yīng)用快速入門教程
Dapr?是一個(gè)可移植的、事件驅(qū)動(dòng)的運(yùn)行時(shí),它使任何開發(fā)人員能夠輕松構(gòu)建出彈性的、無(wú)狀態(tài)和有狀態(tài)的應(yīng)用程序,并可運(yùn)行在云平臺(tái)或邊緣計(jì)算中,它同時(shí)也支持多種編程語(yǔ)言和開發(fā)框架,本文重點(diǎn)介紹NodeJS云原生微服務(wù)應(yīng)用,感興趣的朋友一起看看吧2022-07-07如何在node.js中使用?JsonWebToken模塊進(jìn)行token加密
目前在web框架中最流行的身份驗(yàn)證是使用jsonwebtoken,簡(jiǎn)稱jwt.可以設(shè)置加密方式,過(guò)期時(shí)間,存放個(gè)人信息,逆解析,下面這篇文章主要給大家介紹了關(guān)于如何在node.js中使用?JsonWebToken模塊進(jìn)行token加密的相關(guān)資料,需要的朋友可以參考下2023-03-03DevEco?Studio設(shè)置Nodejs提示路徑只能包含英文、數(shù)字、下劃線等解決辦法
這篇文章主要給大家介紹了關(guān)于DevEco?Studio設(shè)置Nodejs提示路徑只能包含英文、數(shù)字、下劃線等的解決辦法,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01詳解node.js平臺(tái)下Express的session與cookie模塊包的配置
本篇文章主要介紹了詳解node.js平臺(tái)下Express的session與cookie模塊包的配置,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04Node.js Continuation Passing Style( CPS與
這篇文章主要介紹了Node.js Continuation Passing Style,將回調(diào)函數(shù)作為參數(shù)傳遞,這種書寫方式通常被稱為Continuation Passing Style(CPS),它的本質(zhì)仍然是一個(gè)高階函數(shù),CPS最初是各大語(yǔ)言中對(duì)排序算法的實(shí)現(xiàn)2022-06-06Node.js實(shí)現(xiàn)用戶評(píng)論社區(qū)功能(體驗(yàn)前后端開發(fā)的樂趣)
這篇文章主要介紹了Node.js實(shí)現(xiàn)用戶評(píng)論社區(qū)(體驗(yàn)前后端開發(fā)的樂趣) ,需要的朋友可以參考下2019-05-05node.js利用redis數(shù)據(jù)庫(kù)緩存數(shù)據(jù)的方法
Redis數(shù)據(jù)庫(kù)采用極簡(jiǎn)的設(shè)計(jì)思想,最新版的源碼包還不到2Mb。其在使用上也有別于一般的數(shù)據(jù)庫(kù)。下面這篇文章就來(lái)給大家介紹了node.js利用redis數(shù)據(jù)庫(kù)緩存數(shù)據(jù)的方法,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-03-03