如何用NodeJS读取分析Nginx错误日志

栏目: Node.js · 发布时间: 5年前

内容简介:网上很少看到有用NodeJS运维系列文章,后续我会更新一些NodeJS运维相关的内容又或者说让我们更加的深入了解一些服务器的知识以及自动化运维方面的基础知识 为什么要做错误日志分析,因为网上这方面的工具不多我找到一个goaccess但是都是分析成功日志以及用户访问趋势,找了半天没找着自己想要的索性就自己利用Node造一个首先我们要读取Nginx日志,我们可以看到Nginx的错误日志格式一般都是这样子,需要注意的是Nginx的错误日志格式是差不多的因为无法设置日志格式只能设置日志错误等级所以我们分析的时候很方

网上很少看到有用NodeJS运维系列文章,后续我会更新一些NodeJS运维相关的内容又或者说让我们更加的深入了解一些服务器的知识以及自动化运维方面的基础知识 为什么要做错误日志分析,因为网上这方面的 工具 不多我找到一个goaccess但是都是分析成功日志以及用户访问趋势,找了半天没找着自己想要的索性就自己利用Node造一个

错误日志分析

首先我们要读取Nginx日志,我们可以看到Nginx的错误日志格式一般都是这样子,需要注意的是Nginx的错误日志格式是差不多的因为无法设置日志格式只能设置日志错误等级所以我们分析的时候很方便

如何用NodeJS读取分析Nginx错误日志
这里我们用到 readline

逐行读取,简单来说可以做

  • 文件逐行读取:比如说进行日志分析。
  • 自动完成:比如输入npm,自动提示"help init install"。
  • 命令行工具:比如npm init这种问答式的脚手架工具。 这里我们主要做日志分析其他的感兴趣可以琢磨一下

实现方法

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和end

如何用NodeJS读取分析Nginx错误日志

所以我们每次可以在读取完之后记录一下当前文件字节大小下一次读取文件就是可以用该文件上次的大小开始读取

let size = fs.statSync(path.join(__dirname, '../public/api.err.log')).size;
复制代码

我们可以对比一下每次从0字节开始读取和从指定字节读取

如何用NodeJS读取分析Nginx错误日志

保存数据进行分析

这里我是用node-schedule这个库进行定时保存错误日志和 linux 的cron差不多,用的 mongodb 保存数据,这里更推荐大家用elasticsearch来做日志分析

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事件的源码,想继续深入的同学可以看看全部的源码

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在某一天访问出错最多的原因

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
	}
 

);
复制代码

通过这次日志分析学习到很多东西,欢迎大家和我交流,有问题的同学可以在下面留言


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏编程权威指南

游戏编程权威指南

Mike McShaffry 麦克沙福瑞、David “Rez” Graham 格雷海姆 / 师蓉、李静、李青翠 / 人民邮电 / 2016-3 / 99.00元

全书分为4个部分共24章。首部分是游戏编程基础,主要介绍了游戏编程的定义、游戏架构等基础知识。 第二部分是让游戏跑起来,主要介绍了初始化和关闭代码、主循环、游戏主题和用户界面等。 第三部分是核心游戏技术,主要介绍了一些*为复杂的代码 示例,如3D编程、游戏音频、物理和AI编程等。 第四部分是综合应用,主要介绍了网络编程、多道程序设计和用C#创建工具等,并利用前面所讲的 知识开发出......一起来看看 《游戏编程权威指南》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具