nodejs-日志收集

开发过程中,日志记录是必不可少的事情,尤其是生产系统中经常无法调试,因此日志就成了重要的调试信息来源。

1.expressWinston

访问日志一般用来记录每个客户端对应用的访问请求。在Web应用中,主要记录HTTP请求中的关键数据。如下所示记录:

{
    "res":{
        "statusCode":200
    },
    "req":{
        "url":"/user/login",
        "headers":{
            "host":"localhost:8001",
            "connection":"keep-alive",
            "content-length":"348",
            "postman-token":"0ef999d7-1b51-4bc5-c4d8-f067f165b2be",
            "cache-control":"no-cache",
            "origin":"chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop",
            "user-agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
            "content-type":"multipart/form-data; boundary=----WebKitFormBoundaryef7X45UMFFjdZwsB",
            "accept":"*/*",
            "dnt":"1",
            "accept-encoding":"gzip, deflate, br",
            "accept-language":"zh-CN,zh;q=0.8,en;q=0.6",
            "cookie":"SID=s%3AzPVGIcZLGk9osGdrAOpF9BULNZJnGwkj.INJURLoEcBKNFZlvCNonwpqFJq56lXlpQFIu15c6N1Y"
        },
        "method":"POST",
        "httpVersion":"1.1",
        "originalUrl":"/user/login",
        "query":{

        }
    },
    "responseTime":39,
    "level":"info",
    "message":"HTTP POST /user/login",
    "timestamp":"2017-09-12T08:25:44.568Z"
}

中间件winston express-winston提供了请求的完整记录,记录的内容有:

  • res结果
  • req请求包含:url, headlers, method, httpVersion, originalUrl, query六大项
  • responseTime相应时间
  • level当前日志等级
  • message 请求基本信息
  • timestamp 时间戳

其中对于日志中是否包含哪些信息,中间件也做了比较好的支持:

expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');

参考:express-winston

记录日志的目的是为了分析,当前的这些数据已经足够用来帮助分析Web应用的用户分布情况、服务器端的相应时间、相应状态和客户端类型等。通过这些数据反过来帮助我们改进和提升网站。在nodejs中可以使用express-winston将日志打印在控制台,也可以将其打印在本地文件中,还可以将其写入至数据库中。写法如下:

import winston from 'winston';
import expressWinston from 'express-winston';
import winstonMongodb from 'winston-mongodb';

const MongoDB = winstonMongodb.MongoDB;

expressWinston.requestWhitelist.push('body');
app.use(expressWinston.logger({
    transports: [
        // 控制台
        new winston.transports.Console({
            json: true,
            colorize: true
        }),
        // 文件
        new winston.transports.File({
          filename: './logs/success.log'
        }),
        // mongodb数据库
        new winston.transports.MongoDB({ 
            db: config.logsUrl
        })
    ],
    meta: true,
    msg: "HTTP {{req.method}} {{req.url}}",
    expressFormat: true,
    colorize: true,
    ignoreRoute: function (req, res) { return false; }
}));

其中db的写法参考如下,常见问题:

db : 'mongodb://localhost:27017/Book-catalog',

有的开发者可能不太了解,会选择将一些日志写入到数据库中。数据库比日志好的地方在于它是结构化的数据,可以直接使用SQL语言进行查询和统计,日志文件则需要在加工之后才能分析。
但是日志文件和数据库在写入性能上是两个级别,数据库在写入过程中要经历一系列处理,比如锁表、日志等操作。写日志文件则是直接将数据写到磁盘上。相比之下,写日志是轻量型操作,将日志分析和日志记录步骤分离开来是较好的选择。

线上业务可能访问量巨大,产生的日志也可能是大量的,上述示例只是简单的将普通日志和异常日志分开存放至两个文件中,日志过多时也不便产看。为此,将产生的日志按日期分割是必要的。expressWinston可以使用winston-daily-rotate-file,引入第三个库,目前看有点略麻烦,同时还不能将自己的输出打入到日志中,我们准备介绍下一个日志工具log4js

2.log4js

Node.js,已经有现成的开源日志模块,就是log4js,源码地址:点击打开链接
项目引用方法:

npm install log4js

用法也是非常见简单:

var log4js = require('log4js');
var logger = log4js.getLogger();
logger.level = 'debug'; // default level is OFF - which means no logs at all.
logger.debug("Some debug messages");

配置

log4js.configure(object || string)

配置属性包含有:

  • levels:用来定义日志等级
  • appenders:map结构,定义日志输入,必需定义type字段
  • categories:map结构,定义分类,必需定义default分类,分离里面包含appenders和level属性
  • pm2:pm2日志

实例1

const log4js = require('log4js');
log4js.configure({
  appenders: {
    out: { type: 'stdout' },
    app: { type: 'file', filename: 'application.log' }
  },
  categories: {
    default: { appenders: [ 'out', 'app' ], level: 'debug' }
  }
});

代码中定义了两个appenders,分别为out和app。out使用stdout追加日志方法,独立输出。app使用文件作为输入,将内容输出到application.log

Loggers

log4js.getLogger([category])

输入哪种分类的log

Shutdown

log4js.shutdown(cb)

shutdown方法用于关闭log的接收,结束所有写日志事件。

Custom Layouts

log4js.addLayout(type, fn)

用户自定义数据记录格式。

实例2

log4js.configure({
  appenders: { 'out': { type: 'stdout', layout: { type: 'basic' } } },
  categories: { default: { appenders: ['out'], level: 'info' } }
});
const logger = log4js.getLogger('cheese');
logger.error('Cheese is too ripe!');

该配置替换stdout的默认格式coloured,替换为basic。输入为:

[2017-03-30 07:57:00.113] [ERROR] cheese - Cheese is too ripe!

实例3

log4js.configure({
  appenders: { out: { type: 'stdout', layout: { type: 'messagePassThrough' } } },
  categories: { default: { appenders: [ 'out' ], level: 'info' } }
});
const logger = log4js.getLogger('cheese');
const cheeseName = 'gouda';
logger.error('Cheese is too ripe! Cheese was: ', cheeseName);

该配置替换stdout的默认格式coloured,替换为messagePassThrough。输入为:

Cheese is too ripe! Cheese was: gouda

实例4
不得不说的type: 'pattern',通过正则输出相应的格式化数据:

log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: {
      type: 'pattern',
      pattern: '%d %p %c %X{user} %m%n'
    }}
  },
  categories: { default: { appenders: ['out'], level: 'info' } }
});
const logger = log4js.getLogger();
logger.addContext('user', 'charlie');
logger.info('doing something.');

通过正则匹配也可以定义传递变量,如上代码的user。输出为:

2017-06-01 08:32:56.283 INFO default charlie doing something.

实例5
定义自己的输出格式,type: 'json',如下所示:

const log4js = require('log4js');

log4js.addLayout('json', function(config) {
  return function(logEvent) { return JSON.stringify(logEvent) + config.separator; }
});

log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'json', separator: ',' } }
  },
  categories: {
    default: { appenders: ['out'], level: 'info' }
  }
});

const logger = log4js.getLogger('json-test');
logger.info('this is just a test');
logger.error('of a custom appender');
logger.warn('that outputs json');
log4js.shutdown(() => {});

输出为:

{"startTime":"2017-06-05T22:23:08.479Z","categoryName":"json-test","data":["this is just a test"],"level":{"level":20000,"levelStr":"INFO"},"context":{}},
{"startTime":"2017-06-05T22:23:08.483Z","categoryName":"json-test","data":["of a custom appender"],"level":{"level":40000,"levelStr":"ERROR"},"context":{}},
{"startTime":"2017-06-05T22:23:08.483Z","categoryName":"json-test","data":["that outputs json"],"level":{"level":30000,"levelStr":"WARN"},"context":{}},

具体使用

新建log.js文件,内容如下所示:

var log4js = require('log4js');
log4js.configure({
    appenders: {
        out: { type: 'console' }, //控制台输出  
        app: {
            type: "dateFile",
            filename: 'logs/log.log',
            pattern: "_yyyy-MM-dd",
            alwaysIncludePattern: false,
        }//日期文件格式  
    },
    categories: {
        default: { appenders: ['out'], level: 'info' },
        logFile: { appenders: ['out', 'app'], level: 'ALL' },
    },
    replaceConsole: true,   //替换console.log  
});

var fileLog = log4js.getLogger('logFile');
exports.logger = fileLog;
exports.use = function (app) {
    //页面请求日志
    app.use(log4js.connectLogger(fileLog));
}

输入的log如下所示:

[2017-09-18 20:37:02.366] [INFO] logFile - ::1 - - "POST /user/login HTTP/1.1" 200 393 "" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"

你可能感兴趣的:(nodejs-日志收集)