網上不多看到有用NodeJS運維繫列文章,後續我會更新一些NodeJS運維相關的內容又或者說讓咱們更加的深刻了解一些服務器的知識以及自動化運維方面的基礎知識 爲何要作錯誤日誌分析,由於網上這方面的工具很少我找到一個goaccess可是都是分析成功日誌以及用戶訪問趨勢,找了半天沒找着本身想要的索性就本身利用Node造一個javascript
首先咱們要讀取Nginx日誌,咱們能夠看到Nginx的錯誤日誌格式通常都是這樣子,須要注意的是Nginx的錯誤日誌格式是差很少的由於沒法設置日誌格式只能設置日誌錯誤等級因此咱們分析的時候很方便 html
這裏咱們用到 readline逐行讀取,簡單來講能夠作const readline = require('readline');
const fs = require('fs');
const path = require('path');
console.time('readline-time')
const rl = readline.createInterface({
input: fs.createReadStream(path.join(__dirname, '../public/api.err.log'), {
start: 0,
end: Infinity
}),
});
let count = 0;
rl.on('line', (line) => {
const arr = line.split(', ');
const time = arr[0].split('*')[0].split('[')[0].replace(/\//g, '-');//獲取到時間
const error = arr[0].split('*')[1].split(/\d\s/)[1];//錯誤緣由
const client = arr[1].split(' ')[1];//請求的客戶端
const server = arr[2].split(' ')[1];//請求的網址
const url = arr[3].match(/\s\/(\S*)\s/)[0].trim()//獲取請求連接
const upstream = arr[4].match(/(?<=").*?(?=")/g)[0];//獲取上游
const host = arr[5].match(/(?<=").*?(?=")/g)[0];//獲取host
const referrer = arr[6] ? arr[6].match(/(?<=").*?(?=")/g)[0] : '';//來源
console.log(`時間:${time}-緣由:${error}-客戶端:${client}-網址:${server}-地址:${url}-上游:${upstream}-主機:${host}-來源:${referrer}`);
count++;
});
rl.on('close', () => {
let size = fs.statSync(path.join(__dirname, '../public/api.err.log')).size;
console.log(`讀取完畢:${count};文件位置:${size % 2 === 0}`);
console.timeEnd('readline-time')
});
複製代碼
上面代碼有幾點須要注意的是會建立一個文件可讀流而後因爲演示因此我是直接找的本地地址若是是生產環境的話你們能夠直接填寫服務器上的錯誤日誌地址,若是沒有Nginx錯誤日誌分割的話天天會產生不少日誌,createReadStream讀取幾十M的文件還好若是讀取幾百M或者上G的容量日誌這會形成性能問題,因此咱們須要在每次createReadStream不必每次從0字節開始讀取,ceateReadStream提供了start和endjava
因此咱們每次能夠在讀取完以後記錄一下當前文件字節大小下一次讀取文件就是能夠用該文件上次的大小開始讀取let size = fs.statSync(path.join(__dirname, '../public/api.err.log')).size;
複製代碼
咱們能夠對比一下每次從0字節開始讀取和從指定字節讀取node
這裏我是用node-schedule這個庫進行定時保存錯誤日誌和linux的cron差很少,用的mongodb保存數據,這裏更推薦你們用elasticsearch來作日誌分析linux
rl.on('close', async () => {
let count = 0;
for (let i of rlist) {
count++;
if (count % 500 === 0) {
const res = await global.db.collection('logs').bulkWrite(rlist.slice(count, count + 500), { ordered: false, w: 1 }).catch(err => { console.error(`批量插入出錯${err}`) });
} else if (count === rlist.length - 1) {
//批量插入 數據
const res = await global.db.collection('logs').bulkWrite(rlist.slice(rlist - (rlist % 500), rlist.length), { ordered: false, w: 1 });
let size = fs.statSync(addres).size;
size = size % 2 === 0 ? size : size + 1;//保證字節大小是偶數 否則會出現讀取上行內容不完整的狀況
count = 0;
rlist.length = [];
//更新數據庫裏面文件的size
global.db.collection('tasks').updateOne({ _id: addre }, { $set: { _id: addre, size, date: +new Date() } }, { upsert: true });
}
}
resolve(true);
})
複製代碼
上面主要是500條保存一次,由於我用的是批量插入而後mongodb有限制一次性最多插入16M數據的限制,因此你們看本身清空決定一次性插入多少條 猶豫對readline的實現比較感興趣,就去翻閱了一下源碼發現並非咱們想的那麼複雜,readline源碼,下面貼一下line事件的源碼,想繼續深刻的同窗能夠看看所有的源碼git
if (typeof s === 'string' && s) {
var lines = s.split(/\r\n|\n|\r/);
for (var i = 0, len = lines.length; i < len; i++) {
if (i > 0) {
this._line();
}
this._insertString(lines[i]);
}
}
...
Interface.prototype._line = function() {
const line = this._addHistory();
this.clearLine();
this._onLine(line);
};
...
Interface.prototype._onLine = function(line) {
if (this._questionCallback) {
var cb = this._questionCallback;
this._questionCallback = null;
this.setPrompt(this._oldPrompt);
cb(line);
} else {
this.emit('line', line);
}
};
複製代碼
保存的數據須要進行分析好比哪一個IP訪問最多哪條錯誤最多能夠用聚合來進行分析貼出示例分析某個IP在某一天訪問出錯最多的緣由github
db.logs.aggregate(
// Pipeline
[
// Stage 1
{
$group: {
'_id': { 'client': '114.112.163.28', 'server': '$server', 'error': '$error', 'url': '$url', 'upstream': '$upstream','date':'$date' ,'msg':'$msg' } ,
'date':{'$addToSet':'$date'},
count: { '$sum': 1 }
}
},
// Stage 2
{
$match: {
count: { $gte: 1 },
date: ['2019-05-10']
}
},
{
$sort: {
count: -1
}
},
],
// Options
{
cursor: {
batchSize: 50
},
allowDiskUse: true
}
);
複製代碼
經過此次日誌分析學習到不少東西,歡迎你們和我交流,有問題的同窗能夠在下面留言mongodb