node.js日志组件log4js

参考:
Log4js配置详解
Node.js 之 log4js 完全讲解

log4js 是 Node.js 日志处理的模块,支持日志分级、日志分类、日志落盘。

简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const log4js = require('log4js');

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

const logger = log4js.getLogger();
logger.info("Time:", new Date());

运行控制台输出

1
[2019-07-15T10:20:30.398] [INFO] default - test

调用 .getLogger() 可以获得 log4js 的 Logger 实例,这个实例的用法与 console 是一致的,可以调用.debug(也有 .info、.error 等方法)来输出日志。
这里的配置我们后文会一一介绍,接下让我们先熟悉几个 log4js 中的概念。

Level

这个理解起来不难,就是日志的分级。日志有了分级,log4js 才能更好地为我们展示日志(不同级别的日志在控制台中采用不同的颜色,比如 error 通常是红色的),在生产可以有选择的落盘日志,比如避免一些属于.debug才用的敏感信息被泄露出来。

log4js 的日志分为九个等级,各个级别的名字和权重如下:

1
2
3
4
5
6
7
8
9
ALL: new Level(Number.MIN_VALUE, "ALL"),
TRACE: new Level(5000, "TRACE"),
DEBUG: new Level(10000, "DEBUG"),
INFO: new Level(20000, "INFO"),
WARN: new Level(30000, "WARN"),
ERROR: new Level(40000, "ERROR"),
FATAL: new Level(50000, "FATAL"),
MARK: new Level(9007199254740992, "MARK"), // 2^53
OFF: new Level(Number.MAX_VALUE, "OFF")

ALL OFF 这两个等级并不会直接在业务代码中使用。剩下的七个即分别对应 Logger 实例的七个方法,.trace .debug .info …。也就是说,你在调用这些方法的时候,就相当于为这些日志定了级。因此,之前的 [2016-08-21 00:01:24.852] [DEBUG] [default] - Time: 2016-08-20T16:01:24.852Z 中的 DEBUG 既是这条日志的级别。

类型

log4js 还有一个概念就是 category(类型),你可以设置一个 Logger 实例的类型,按照另外一个维度来区分日志:

1
2
3
4
const log4js = require('log4js');

const logger = log4js.getLogger('example');
logger.debug("Time:", new Date());

在通过 getLogger 获取 Logger 实例时,唯一可以传的一个参数就是 loggerCategory(如’example’),通过这个参数来指定 Logger 实例属于哪个类别。

我们修改一下之前的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const log4js = require('log4js');

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

const logger = log4js.getLogger('example');
logger.info("Time:", new Date());

运行后控制台输出:

1
[2019-07-15T10:31:05.149] [INFO] example - Time: 2019-07-15T02:31:05.149Z

[default] 变成了 example
类别比级别更为灵活,为日志了提供了第二个区分的维度。

Appender

在 log4js 中,日志的出口问题(即日志输出到哪里)由 Appender 来解决。

默认 appender

1
2
3
4
5
6
// log4js.js
defaultConfig = {
appenders: [{
type: "console"
}]
}

在没有对 log4js 进行任何配置的时候,默认将日志都输出到了控制台。我们可以通过log4js.configure来设置我们想要的 appender,我们修改一下之前的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const log4js = require('log4js');

log4js.configure({
appenders: {
out: { type: 'stdout' },
file: {
type: 'file',
filename: 'default.log'
},
},
categories: {
default: { appenders: ['file'], level: 'info' }
}

});

const logger = log4js.getLogger('custom-appender');
logger.info("Time:", new Date());

运行代码,log4js 在当前目录创建了一个名为default.log 文件,[2019-07-15T10:45:35.192] [INFO] custom-appender - Time: 2019-07-15T02:45:35.192Z输出到了该文件中。

Console 和 File 都是 log4js 提供的 appender,除此之外还有:

  1. DateFile:日志输出到文件,日志文件可以安特定的日期模式滚动,例如今天输出到default-2016-08-21.log,明天输出到 default-2016-08-22.log;
  2. SMTP:输出日志到邮件;
  3. Mailgun:通过 Mailgun API 输出日志到 Mailgun;
  4. levelFilter 可以通过 level 过滤;
    等等其他一些 appender,到这里可以看到全部的列表。

过滤级别和类别

我们可以调整 appender 的配置,对日志的级别和类别进行过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const log4js = require('log4js');

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

log4js.configure({
appenders: {
consoleJson: { type: 'console', layout: { type: 'json' } },
console: { type: 'console' }
},
categories: {
default: { appenders: ['console'], level: 'info' },
all: { appenders: ['consoleJson', 'console'], level: 'info' }
}
});

const logger1 = log4js.getLogger();
const logger2 = log4js.getLogger('all');
logger1.debug("Time:", new Date());
logger2.info("Time:", new Date());
log4js.shutdown(() => {});

运行结果:

1
2
{"startTime":"2019-07-15T13:53:28.174Z","categoryName":"all","data":["Time:","2019-07-15T13:53:28.174Z"],"level":{"level":20000,"levelStr":"INFO","colour":"green"},"context":{},"pid":12642}
[2019-07-15T21:53:28.174] [INFO] all - Time: 2019-07-15T13:53:28.174Z

  1. 使用 level 来对日志的级别进行过滤,所有权重大于或者等于info的日志将会输出。这也是之前提到的日志级别权重的意义;
  2. 通过 category 来选择要输出日志的类别,该配置也接受一个数组,例如 [‘consoleJson’, ‘console’],这样配置两个类别的日志都将输出到文件中。

Layout

Layout 是 log4js 提供的高级功能,通过 layout 我们可以自定义每一条输出日志的格式。log4js 内置了四中类型的格式:

  1. messagePassThrough:仅仅输出日志的内容;
  2. basic:在日志的内容前面会加上时间、日志的级别和类别,通常日志的默认 layout;
  3. colored/coloured:在 basic 的基础上给日志加上颜色,appender Console 默认使用的就是这个 layout;
  4. pattern:这是一种特殊类型,可以通过它来定义任何你想要的格式。

一个 pattern 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const log4js = require('log4js');

log4js.configure({
appenders: {
console: { type: 'console', layout: { type: 'pattern', pattern: '[%r] [%[%5.5p%]] - %m%n' } },
},
categories: {
default: { appenders: ['console'], level: 'info' },
}
});

const logger = log4js.getLogger();
logger.info("Time:", new Date());
log4js.shutdown(() => {});

%r %p $m $n 是 log4js 内置的包含说明符,可以借此来输出一些 meta 的信息,更多细节,可以参考 log4js 的文档。

这里,我们总结一下Logger、Appender 和 Layout 的定位。

  1. Logger 输出的日志内容
  2. Appender 日志输出到哪
  3. Layout 如何输出日志

实战

一个应用日志配置的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const log4js = require('log4js');
const config = require('./index');
const moment = require('moment');
const util = require('util');

log4js.addLayout('json', () => {
return (logEvent) => {
const categoryName = logEvent.categoryName;
const level = logEvent.level && logEvent.level.levelStr;
const pid = logEvent.pid;
const cluster = logEvent.cluster;
const clusterWorkerId = cluster && cluster.workerId;
const clusterWorker = cluster && cluster.worker;
const time = moment().format('YYYY-MM-DD HH:mm:ss');
const data = logEvent.data;
let message = util.format(...data);
message = message.replace(/\\"/g, '"');
const log = { time, categoryName, level, message, pid, clusterWorkerId, clusterWorker };
return JSON.stringify(log);
};
});

const logConfig = () => {
log4js.configure({
appenders: {
console: {
type: 'console',
},
file: {
type: 'file',
filename: config.logPath + 'file.log',
pattern: '-yyyy-MM-dd',
alwaysIncludePattern: true,
layout: {
type: 'json',
},
},
},
categories: {
default: { appenders: ['console'], level: config.logLevel },
file: { appenders: ['file', 'console'], level: config.logLevel },
},
pm2: true,
});
};

module.exports = logConfig;

看看我们做了哪些事情:

  1. 配置了两个appender,一个为console,另一个为file,file输出到一个滚动的文件中;
  2. 使用log4js.getLogger(‘file’) 获取一个类别为 file 的 Logger 实例,传递给log4js.connectLogger 中间件,这个中间件收集访问信息,通过这个实例打出。
0%