因为是koa中间件,所以最起码要先搭起一个koa服务。
安装log4js模块
npm install log4js --save
说白了就是配置打印输出源
type:
"type":"console",即为控制台打印,多数用于开发测试。
"type":"file",表示日志输出为普通文件,在此种配置下日志会输出到目标文件夹的目标文件中,并会随着文件大小的变化自动份文件.。
"type":"dateFile",表示是输出按时间分文件的日志,在此种配置下,日志会输出到目标目录下,并以时间格式命名,随着时间的推移,以时间格式命名的文件如果尚未存在,则自动创建新的文件.。
filename:日志文件路径。
maxLogSize:只在type:file模式有效,表示文件多大时才会创建下一个文件(xxx.log.1之类)单位是字节,实际设置时具体的值根据业务来定,但是不推荐大于100Mb.。
pattern:只在type:dateFile模式有效,表示一个文件的时间命名模式,在生成文件中会依照pattern配置来在filename的文件结尾追加一个时间串来命名文件。
alwaysIncludePattern:只在type:dateFile模式有效,这个配置为ture.即最终的日志路径文件名为filename+pattern
backups:只在type:file模式有效,表示备份的文件数量,如果文件过多则会将最旧的删除。
里面存着一个个的logger分类,就是log4js.getLogger(分类),不啰嗦看下面代码
{
appenders: { //日志输出方式
cheese: {
type: 'dateFile', //按日期创建文件,文件名为 filename + pattern
filename: 'logs/',
pattern: 'yyyy-MM-dd.log',
alwaysIncludePattern: true
}
},
categories: { //logger分类,如log4js.getLogger('default')
default: {
appenders: ['cheese'],
level: 'info'
}
}
}
创建中间件目录,在目录下创建index.js文件(入口文件),再建一个access.js文件(客户端信息)。
index.js
主要是按app.js中传进来的参数配置log4js,如果是开发环境则在appenders上添加dev = {type: "console"},表示开发环境中可在控制台打印。还有在ctx上添加了几个log4js的方法。
const log4js = require('log4js');
const clientInfo = require('./access');
// log4js中日志级别
const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
module.exports = (appender, options) => {
// appenders
let appenders = {
cheese: appender //app.js中配置的appender
};
// 如果是开发环境就往appenders添加dev
if(options.env === 'development'){
appenders.dev = {type: "console"}
}
// log4js配置
log4js.configure({
appenders: appenders,
categories: {
default: { //只写了一个默认分类
appenders: Object.keys(appenders),
level: options.level //app.js中设置的level
}
}
});
const logger = log4js.getLogger();
return async (ctx, next) => {
// 在ctx上添加logger的几个方法
let ctxLogger = {};
levels.forEach(level => {
ctxLogger[level] = message => {
// clientInfo()返回客户端信息
logger[level](clientInfo(ctx, message));
}
})
ctx.log = ctxLogger;
ctx.log.startTime = Date.now();
await next();
}
}
access.js
输出内容格式化,包含一些客户端信息。
/**
* @param {*} req ctx.req
* @method 获取客户端ip地址
*/
function getClientIp(req){
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
}
function isMobile(userAgent){
// 判断是移动端还是pc端
return /Mobile/.test(userAgent) ? 'Mobile' : 'PC';
}
module.exports = (ctx, message) => {
let client = {
ip: getClientIp(ctx.req),
method: ctx.request.method,
path: ctx.request.path,
referer: ctx.request.headers['referer'],
userAgent: isMobile(ctx.request.headers['user-agent']),
responseTime: (Date.now()-ctx.log.startTime)/1000 + 's',
message: message
}
// 返回客户端信息交给logger打印
return JSON.stringify(client);
}
app.js中使用
const myLog = require('koa-sam-log');
// myLog函数传入一个appender和一些其他配置
app.use(myLog({
type: 'dateFile', //按日期创建文件,文件名为 filename + pattern
filename: 'logs/',
pattern: 'yyyy-MM-dd.log',
alwaysIncludePattern: true
},{
env: app.env, //如果是development则可以同时在控制台中打印
level: 'info' //logger level
}));
最后日志打印样式
[2018-11-07T00:24:08.406] [INFO] default - {"ip":"::1","method":"GET","path":"/","userAgent":"PC","responseTime":"0.001s","message":"恭喜你用koa-sam-log中间件成功打印日志"}
该中间件我已发布到npm上,名为koa-sam-log,npm install一下即可在项目中使用
npm install koa-sam-log --save
npm地址 github地址
既然都写了日志了,自然要分析其中信息。
日志都是文本文件,可以用正则分析,但文件基本都挺大,这时就要先用流读取文本内容了,但readStream有个问题就是每次最多只读64k,你根本不知道文本读到哪了,因为我们日志内容都是每行打印,所以这里就可以使用readline
代码如下
const fs = require('fs');
const readline = require('readline');
const readliner = readline.createInterface({
input: fs.createReadStream('./logs/2018-11-05.log')
})
// 日志行数
let count = 0;
// 日志数据统计
let data = {
INFO: 0,
ERROR: 0,
FATAL: 0,
WARN: 0,
ips: [],
mobileUser: 0,
pcUser: 0
}
let levelReg = /(\[INFO\]|\[ERROR\]|\[FATAL\]|\[WARN\])/;
let startTime = Date.now();
readliner.on('line', line => {
// 日志信息类型数量统计
let key = line.match(levelReg)[0].slice(1, -1);
data[key]++;
// 用户设备统计
if(line.indexOf('"userAgent"') != -1){
if (line.match(/userAgent\":\"(.+?)\"/)[1] === 'Mobile') {
data.mobileUser++;
}else{
data.pcUser++;
}
}
// 用户ip地址统计
if(line.indexOf('"ip"') != -1){
let ip = line.match(/ip\":\"(.+?)\"/)[1];
data.ips.push(ip);
}
count++;
})
readliner.on('close', () => {
// ips数组去重
data.ips = Array.from(new Set(data.ips));
console.log(data, count);
console.log(Date.now() - startTime) //这里测试31104行日志,用时232ms
})
最后统计的数据大概是这样
{ INFO: 10,
ERROR: 12,
FATAL: 0,
WARN: 8,
ips: [ '::1', '::ffff:10.0.223.238' ],
mobileUser: 13,
pcUser: 17
}
readline参考文章