用log4js写一个koa2日志中间件

因为是koa中间件,所以最起码要先搭起一个koa服务。

先来介绍一下log4js

安装log4js模块

npm install log4js --save

appenders

说白了就是配置打印输出源

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模式有效,表示备份的文件数量,如果文件过多则会将最旧的删除。

categories

里面存着一个个的logger分类,就是log4js.getLogger(分类),不啰嗦看下面代码

一份简单的log4js配置

{
    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参考文章

发布了63 篇原创文章 · 获赞 18 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/samfung09/article/details/83689133