nodejs收集日誌,rsyslog同步收集入es的實施

之前寫過相關的2篇文章:

1,前端異常監控系統的落地

2,ElasticSearch和Gome-error-report的安裝教程

其實中間還忽略了一個問題,在文章2中,我只是在GER-server的項目中增加了一個ES的create log的介面,這麼做其實是一個不太好的方案,因為通過API的方式遠程寫日誌,在大並發的情況下並不是最優的,會遇到一些瓶頸和寫入失敗的問題,如何能夠避免呢?換了公司之後,自己在新公司做了一套新的日誌收集方案,這裡記錄並且分享給大家。

如果沒有讀過前兩篇文章的建議去閱讀前兩篇之後再來看這一篇,本文主要是說日誌的落地和同步收集的實施。

首先來看一下這張圖:

本文主要是說錯誤SDK上報後,我們如何來做負載和日誌的同步,這裡的技術棧包括了rsyslog,es,nodejs,crontab。

一,日誌收集機的配置

首先,一開始設計的是sdk上報後,先推到nginx,拿nginx的access log做落地的,但是後來發現,當SDK上報一條信息,包含多條錯誤日誌時,這種做法很蠢,因為在rsyslog中,都是一條一條去push到es的,中間需要處理的過程比較多,當然可以在nginx那一層拿lua來做一次處理,這裡我選擇使用nodejs。

處理邏輯比較簡單,我貼一下完整代碼:

let express = require(express);nlet app = express();nlet port = 80;nlet fs = require(fs);nlet fsExtra = require(fs-extra);nlet path = require(path);nlet moment = require(moment);nlet accessDir = /var/log/jslogs/;nlet accessPath = accessDir + jserror.access.log;nlet logpath = path.resolve(__dirname, accessPath);nlet async = require(async);nnfsExtra.ensureDirSync(accessDir);nnvar rotatingLogStream = require(file-stream-rotator).getStream({n filename: accessPath,n frequency: "1h",n verbose: false,n max_logs: "5d",n audit_file: "/var/log/jslogs/log-audit.json"n});nnapp.get(/read.gif, (req, res, next) => {n let img = fs.createReadStream(path.resolve(__dirname, ./images/read.gif));n var err_msg = req.query.err_msg;n if (err_msg) {n let logs = [];n let writelogs = [];n let errmsg = decodeURIComponent(err_msg);n // | 會分割成多條n let errLogs = errmsg.split(|);n errLogs.forEach((msg) => {n let params = {n log_master: js,n ext: -n };n msg = msg.replace(/^/g, &);n msg = msg.split(&);n msg.forEach((item) => {n item = item.split(=);n params[item[0]] = item[1];n });n let timestamp = moment().format();n let request_time = moment().format(YYYY-MM-DD hh:mm:ss);n let log = {n project_name: "JS",n @timestamp: timestamp,n request_time: request_time,n message: {n log_master: params.log_master,n msg: params.msg,n projectType: params.projectType,n currentUrl: params.currentUrl,n flashVer: params.flashVer,n level: params.level,n referer: params.referer,n screenSize: params.screenSize,n timestamp: params.timestamp,n userAgent: params.userAgent,n title: params.title,n host: params.host,n colNum: params.colNum,n rowNum: params.rowNum,n targetUrl: params.targetUrl,n ext: params.extn }n }n log = @cee: + JSON.stringify(log) + n;n writelogs.push(function(cb) {ntrotatingLogStream.write(log + n,cb);n });n });n //寫入日誌n async.parallel(writelogs);n img.pipe(res);n } else {n img.pipe(res);n }n});nnapp.use((req, res, next) => {n res.status(404);n res.send(404: File Not Found);n});nnnapp.listen(port);nconsole.log(the server is listen on %s, port);n

上面的代碼可以簡單說一下作用,nodejs監聽80埠,然後get請求有一個read.gif的1x1圖片介面,別人用err_msg=xxx之後訪問,解析出參數最後再落到es格式的日誌,日誌切分用的是file-stream-rotator,每1小時切一次,保留5天內的日誌。

日誌參數詳見GER SDK的上報格式,這裡存的是一致的日誌格式。

然後我們看一下rsyslog的設置,rsyslog分為客戶端和服務端,客戶端為收集日誌的配置,既把nodejs的落地日誌推到服務端機器,服務端則做接收解析和推入es中。

下面說一下rsyslog的安裝和配置,我這邊做的系統是centos6.5,默認安裝的是5.8,需要安裝最新的rsyslog以支持更多的插件和template語法。

wget http://rpms.adiscon.com/v8-stable/rsyslog.reponmv rsyslog.repo /etc/yum.repos.d/rsyslog.reponyum info rsyslog --skip-brokennyum install -y rsyslog rsyslog-elasticsearch rsyslog-mmjsonparsenrsyslogd -versionn

切記要裝rsyslog的elasticsearch和mmjsonparse的擴展,當然這2個模塊,在服務端的rsyslog上安裝即可。

然後我們看一下客戶端是如何配置的,在/etc/rsyslog.conf文件後添加:

module(load="imfile")nruleset(name="remote"){n action(type="omfwd" Protocol="tcp" Target="服務端ip" Port="514") stopn}ninput(type="imfile"n File="/var/log/jslogs/*.log.*"n Facility="user"n Severity="error"n Tag="web_access"n PersistStateInterval="1"n Ruleset="remote")n

這段配置指的是把/var/log/jslogs/下面的所有log,通過tcp推送到服務端的514埠。

然後我們再配置一下rsyslog的開機啟動即可。

chkconfig rsyslog onn

然後我們再配置一下nodejs的開機啟動和pm2的日誌,首先是安裝pm2:

npm install pm2 -gnpm2 install pm2-logrotatennpm install pm2-guin

以上分別為pm2,pm2日誌切割模塊,pm2的可視化server。

然後使用pm2-gui start 啟動可視化的pm2管理界面,默認埠是8088,默認密碼:AuTh.

然後pm2啟動我們上面的nodejs日誌記錄。

pm2 start index.js --name errorNodeLog -o ./logs/access.out.log -e ./logs/error.out.logn

然後我們設置pm2的日誌切割參數:

pm2 set pm2-logrotate:compress true npm2 set pm2-logrotate:rotateInterval * * 1 * * * *n

設置完畢後,我們設置pm2的開機啟動:

pm2 savenpm2 startup centos6n

基本到這裡,我們的客戶端機器就搞定了。當然,安裝pm2之前,如果你是新機器,還得安裝nodejs,這裡推薦用官方的方法,yum安裝:

Installing Node.js via package manager | Node.js

啟動好rsyslog和pm2的nodejs服務後,我們測試一下介面,是否會成功寫入日誌,然後我們開始配置服務端的機器。

二,日誌服務端的配置:

日誌服務的配置,我們要安裝的軟體如下:java,es,nodejs,GER-sever,以及rsyslog。

java比較好安裝,yum list java* 裝1.7或者1.8的都可以,這裡不說了,nodejs安裝同上。

然後是es和GER-server的安裝,文章開頭有講,這裡更新一下mapping的設置:

curl -XPUT esip:port/_template/template_1 -d {"template":"logstash-web_access*","mappings":{"logs":{"properties":{"@timestamp":{"type":"date"},"message":{"properties":{"host":{"type":"string","index":"not_analyzed"}}}}}}} n

自行更換esip和port即可。

然後記得安裝一下可視化的es插件:

elasticsearch/bin/plugin install mobz/elasticsearch-head n

然後訪問 http://{你的ip地址}:9200/_plugin/head/看是否安裝成功,默認是9200埠,如果你改了es的埠,就修改9200地址。

rsyslog的安裝上面說過了,我們直接看服務端的配置,在/etc/rsyslog.conf下加入下面配置:

module(load="imtcp")nmodule(load="mmjsonparse")nmodule(load="omelasticsearch")nninput(type="imtcp" Port="514" Ruleset="apprule")nntemplate(name="DynFile" type="string" string="/var/log/applog/%$year%-%$month%-%$day%/%fromhost-ip%.log")nntemplate(name="logstash-web_access" type="list") {n constant(value="logstash-") property(name="syslogtag" format="json")n constant(value="-")n property(name="timereported" dateFormat="rfc3339" position.from="1" position.to="4")n constant(value=".")n property(name="timereported" dateFormat="rfc3339" position.from="6" position.to="7")n constant(value=".")n property(name="timereported" dateFormat="rfc3339" position.from="9" position.to="10")n}nntemplate(name="es-tpl" type="list"){n constant(value="{"@timestamp":"") property(name="timereported" dateFormat="rfc3339")n constant(value="",") property(name="$!all-json" position.from="2")n}nntemplate( name="nginx-log" type="string" string="%msg%n" )nnruleset(name="apprule") {n action(type="mmjsonparse")n action(type="omfile" DynaFile="DynFile")n action(type="omelasticsearch"n template="es-tpl"n searchIndex="logstash-web_access"n dynSearchIndex="on"n bulkmode="on"n searchType="logs"n queue.type="LinkedList"n queue.size="1000"n queue.dequeuebatchsize="300"n action.resumeretrycount="-1"n server="es-ip"n serverport="es-port"n errorFile="/var/log/applog/es-error.log") stopn}n

請自行替換最後面omelasticsearch的配置,把server和serverport換成對應自己的服務和埠地址,簡單說一下這個配置做了什麼。

首先載入幾個需要用到的module,然後下面定義了入es的template,模板方面的語法可以自己參考一下rsyslog官方的文檔,比較簡單,主要是字元替換和格式替換用。因為我們同步的日誌只有我們nodejs收集的程序日誌,所以訂了一個apprule的規則,最後配置這個apprule規則,先是把日誌json進行解析,然後按照DynFile的template格式寫入日誌,最後再推送到es中。

三,測試:

配置完成後我們通過刷新客戶端的介面來進行測試,可以tail一下2台日誌的日誌做對比,如果同步成功就算大功告成了,當然最後還要查看一下es里是否真正的推入了日誌:

es中如果也推數據成功,根據上一篇文章中的GER-Server來配置可視化的錯誤平台就成了,之前的專欄文章都有說過。

最後,由於es也要做成開機啟動,建議都拿chkconfig來進行維護,使用yum安裝,然後同樣給可視化的平台加上pm2相關的配置和開機啟動。

下面貼一下,crontab下我們如何對es的索引進行管理的腳本:

const request = require(request);nconst moment = require(moment);nconst url = require(./config).url;nnconst date15Ago = moment().subtract(16, d); // 獲取15天之前的日期nconst date15AgoStr = date15Ago.year() + . + (date15Ago.month() + 1) + . + date15Ago.date();nnmodule.exports = function () {n // 刪除15天前的日誌日誌n request.delete({n url: url + date15AgoStr,n json: truen }, function (error, response, body) {n if(!error){n if(response.statusCode == 200){n console.info(success: delete logs of + date15AgoStr);n } else if(response.statusCode == 404) {n console.error(error: error in delete log:nlog does not exist);n } else {n console.error(error: error in delete log:n);n console.error(body);n }n }n });n}n

刪除15天之前的索引腳本。

const request = require(request);nconst moment = require(moment);nconst config = require(./config);nnconst date = moment().subtract(1, d); // 獲取前一天的日期nconst dateStr = date.year() + . + (date.month() + 1) + . + date.date();nnmodule.exports = function () {n getLogInfo(function (data) {n saveLogInfo(data);n });nn function getLogInfo(cb) {n // 統計前一天的日誌信息n request({n url: config.url + dateStr + /logs/_search,n json: true,n body: {n "size" : 0,n "aggs" : {n "projectType" : {n "terms" : {n "field" : "message.projectType"n },n "aggs" : {n "hosts" : {n "terms" : {n "field" : "message.host"n }n }n }n }n }n }}, function (error, response, body) {n // console.log(body);n // console.log(JSON.stringify(body.aggregations.projectType.buckets));n if(!error){n if(response.statusCode == 200){n console.info(success: get log info);n cb(body.aggregations.projectType.buckets);n } else if(response.statusCode == 404){n console.error(error: error in get log info:nlog does not exist);n } else {n console.error(error: error in get log info:n);n console.error(body);n }n }n });n }nn // 存儲前一天的日誌信息n function saveLogInfo(data) {n request.post({n url: config.baseUrl + log_count/ + dateStr,n json: true,n body: {n info: datan }n }, function (error, response, body) {n if(!error){n if(response.statusCode == 201){n console.info(success: save log info);n } else {n console.error(error: error in saving log info:n);n console.error(body);n }n }n });n }n}n

統計前一天有多少域名,多少端(pc|mobile)錯誤count的腳本,然後再反存回es。

最後我們再通過crontab把2個腳本做一個定時執行即可,把入口的js文件開頭加入:

#!/usr/bin/env noden

這一行就可以啦。

ok,做一個記錄,怕過2星期我自己也忘了,多謝大家觀看。


推薦閱讀:

如何評價 Node.js 8.0 ?
Node.js新手在哪兒找小項目練手?
參加第11屆D2前端技術論壇,你有什麼收穫?
Nodejs中 Callback 的執行是否造成阻塞?
node.js 入門請推薦本好的入門書籍?

TAG:Nodejs | 日志 | 前端开发 |