Nodejs日志库log4js快速入门

NodeJs项目中,log4js一般会作为日志库的首选,本篇来快速入门一下log4js的一些配置和使用规则。

本文只介绍常用内容,具体细节请查看log4js官方文档

本文基于log4js@6.9.x

演示过程中使用esmodule规范,在package.json中指定typemodule即可在nodejs中使用esmodule规范。

安装log4js输出第一条日志

直接通过npm进行安装即可:

1
npm install log4js

我们新建index.js并引入log4js测试一下:

1
2
3
4
5
6
// index.js

import log4js from 'log4js'

const logger = log4js.getLogger()
logger.info('abcdefg')

此时我们执行node index.js可以发现控制台并没有任何输出,刚开始就跳坑了吗?

查看官方文档我们可以知道必须要对log4js进行配置,先不关注配置项的规则,我们先写几条试一下:

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
// index.js

import log4js from 'log4js'

// 必须配置,否则log不能输出
log4js.configure({
// 追加器
appenders: {
// 追加器的名字自己取,不限制
console: {
type: 'console'
}
},
// 类别
categories: {
default: {
// 应用哪些追加器
appenders: ['console'],
// 日志级别
level: 'all'
},
}
})

const logger = log4js.getLogger()
logger.info('abcdefg')

此时再通过nodejs运行这个文件就能在控制台看到输出我们的第一条日志了:

log4js的一些概念

前边我们看到了一些如追加器、类别、日志级别等概念,这一节就先说一下这些概念,不然后边容易看不懂。

追加器appenders

追加器负责日志事件的输出。他们可以将事件写入文件、发送电子邮件、将它们存储在数据库中或任何内容。大多数追加程序使用布局将事件序列化为字符串以进行输出。

追加器主要是自定义命名追加器 对 默认追加器对象的映射。是一个对象。

说人话就是我们自己可以定义一个名字作为追加器的key,其value是一个对象,这个对象指向一个log4js定义的追加器。所以这个value必须有一个type属性,type是一个字符串描述使用哪一种追加器,是否需要其他字段取决于type使用哪种追加器。

比如

1
2
3
4
5
6
7
appenders: {
// 这个是我们定义的追加器的名字,用来给类别使用
console: {
// type的值是log4js提供的追加器类型,有些类型需要配置额外字段
type: 'console'
}
}

这里没想到怎么去翻译,所以自定义的key也称为追加器名称了。

这个自定义追加器名称 就是 对log4js某个追加器的自定义配置

类别categories

用于对日志事件进行分组的标签。类别的配置方式与追加器是类似的,也是我们自定义一个类别名称,然后指定这个类别使用哪些追加器以及日志级别等。而且必须要定义一个名为default的类别。类别配置接收三个字段:

  • appenders: 要使用的追加器,是一个字符串数组,这里配置的就是我们自定义的追加器名称。
  • level: 日志级别,很重要,后边会说。这里是对于logger的最低级别限制,如果用了该category的logger以低于该级别输出日志是不会输出的
  • enableCallStack: 可选字段,是一个布尔值,默认为false。

日志级别level

日志级别是日志事件(调试、信息等)的严重性或优先级。追加者是否会看到事件取决于类别的级别。如果此值小于或等于事件的级别,则会将其发送到类别的追加器。

将日志分为不同等级对日志进行分类,优先级由低到高为:

ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF

注意,off是用来关闭日志,而不是作为日志级别

也就是说我们永远不会用logger.off()来输出日志

日志对象logger

这是你的代码与log4js的主界面。记录器实例可能具有在创建实例时定义的可选类别

我们通过log4js.getLogger()得到logger对象

getLogger([category])接收一个可选参数category,类型为字符串。如果不传递就会使用default,这就是我们前边说的必须要定义一个default类别的原因。

logger对象上有一个常用属性level,我们可以对其进行修改,后边会做演示。

日志事件

比如logger.info, logger.error, logger.debug等都是触发日志事件

logger.info, logger.error等事件是表示以指定日志级别输出日志,当然如果日志级别低于category设置的级别是被忽略的。

布局layout

用于将日志事件转换为字符串表示的函数。有些抽象,后边具体说吧。

configure配置

使用log4js的第一步就是要配置configure

配置项

先过一下configure的所有配置项吧:

  • levels: 可选参数,定义日志级别。
  • appenders: 追加器
  • categories: 类别
  • pm2: 可选参数,布尔值,如果使用pm2运行应用需要设置为true
  • pm2InstanceVar: 可选参数,如果未更改pm2的默认配置,一般来说不需要设置
  • disableClustering: 可选参数,布尔值,关于集群配置的,不太懂就不说了。

因为关于追加器、类别等还是有很多可自定义配置的,要放到后边来说,所以我们接下来结合前边说的内容做一些简单的配置演示。

多类别应用

我们运用前边的知识,创建多个appendercategory来测试

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
import log4js from 'log4js'

log4js.configure({
appenders: {
console: {
type: 'console'
},
out: {
type: 'stdout'
}
},
categories: {
default: {
appenders: ['console'],
level: 'all'
},
out: {
appenders: ['out'],
level: 'all'
},
all: {
appenders: ['console', 'out'],
level: 'all'
},
}
})
// 使用type=console的appender
const logger = log4js.getLogger()
logger.info('这是default类别日志输出')
// 使用type=stdout的appender
const outLogger = log4js.getLogger('out')
outLogger.info('这是out类别输出日志')
// 同时使用console和stdout的append
const allLogger = log4js.getLogger('all')
allLogger.info('这是all类别输出日志')

运行该文件,可以看到控制台输出:

可以看到,使用categoryalllogger输出了2条日志

日志级别控制

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
import log4js from 'log4js'

log4js.configure({
appenders: {
console: {
type: 'console'
}
},
categories: {
default: {
appenders: ['console'],
// 注意level设置为error
level: 'error'
}
}
})

// logger受用default这个category,所以最低level设置为error
const logger = log4js.getLogger()
// 由于info级别低于error,所以会被忽略
logger.info('info输出')
// 降低level
logger.level = 'info'
// info级别等于info,所以可以输出
logger.info('降低level后再次以info输出')
// error级别高于info,也可以输出
logger.error('error输出')

执行可以看到控制台输出结果:

由此可见,低于指定level的日志输出会被忽略

追加器appenders

在前边,我们只用到了consolestdout这两个appender,也是最简单的两个,在项目中这肯定是不够的,所以这里我们介绍一些常用的appender

console

console使用NodeJsconsole.log来输出日志到控制台,比较简单,就不再多做演示了。

可配置项:

  • typeconsole
  • layout: 布局放到后边说

注意!

将大量日志输出到控制台可能会导致内存占用上升,尽量不要在生产环境中启用。

可以使用stdout作为appender来代码console以减少内存占用

console总是使用console.log,即便logger.error()输出日志也并不会使用console.error

stdout

stdoutlog4js的默认appender,将所有日志事件写入标准输出流。

可配置项:

  • typestdout
  • layout: 布局放到后边说

stderr

stdout有些相似,不过是将所有日志事件写入标准错误流

可配置项:

  • typestderr
  • layout: 布局放到后边说

logLevelFilter

这个appender很有意思,我们前边是在category设置的level,其实在appender中也可以进行一些限制,就是用到logLevelFilter

可配置项:

  • type: logLevelFilter
  • appender: 真正要用的追加器,比如stdout
  • level: 允许通过追加器的最低日志级别限制
  • maxLevel: 可选参数,允许通过追加器的最大日志级别限制,默认为fatal

如果事件的级别大于或等于level且小于或等于 maxLevel,则它将被发送到追加器。

代码如下:

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
import log4js from 'log4js'

log4js.configure({
appenders: {
console: {
type: 'console'
},
logLevelFilter: {
type: 'logLevelFilter',
appender: 'console',
level: 'error'
}
},
categories: {
default: {
appenders: ['console', 'logLevelFilter'],
level: 'info'
}
}
})

const logger = log4js.getLogger()
// category设置level为info,所以不会被该类别忽略,但是logLevelFIlter appender设置level为error,所以会被该appender忽略,而console appender正常执行
logger.info('以info输出')
// 以error执行则不会被忽略
logger.error('以error输出')

执行该文件的结果如下:

总结:

如果日志事件的级别 低于 类别定义的级别, 该事件会被忽略

如果日志事件的级别 不低于 类别定义的级别,那么该事件会生效,但是如果该类别中应用到了logLevelFilter追加器,需要不低于该追加器定义的级别才会应用该追加器,其他追加器不受影响

file

将日志写入到文件是基础的需求,我们来看一下file appender

将日志事件写入文件。它支持可选的最大文件大小,并将保留可配置数量的备份。

可配置项:

  • typefile
  • filename: 要写入日志的文件的路径
  • maxLogSize: 可选参数,日志的最大大小(以字节为单位)。如果指定为0或不指定,则不会发生日志滚动
  • backups: 可选参数,默认5,日志滚动期间要保留的旧日志文件数
  • layout: 布局放到后边说

在应用程序终止时调用 log4js.shutdown,以确保任何剩余的异步写入都已完成

不限制日志大小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import log4js from 'log4js'

log4js.configure({
appenders: {
file: {
type: 'file',
filename: 'app-log.log'
}
},
categories: {
default: {
appenders: ['file'],
level: 'all'
}
}
})

const logger = log4js.getLogger()
logger.info('这是一行日志输出内容')
log4js.shutdown()

此时多次执行这个文件,会发现在同级目录创建了一个文件app-log.log,内容如下:

限制日志文件大小

代码如下:

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
import log4js from 'log4js'

log4js.configure({
appenders: {
file: {
type: 'file',
filename: 'app-log.log',
// 10kb
maxLogSize: 1024 * 10,
backups: 3,
}
},
categories: {
default: {
appenders: ['file'],
level: 'all'
}
}
})

const logger = log4js.getLogger()
// 循环输出,查看压缩效果
for(let i = 0; i < 100; i ++) {
logger.info('这是一行日志输出内容')
}
log4js.shutdown()

执行一次代码,可以发现app-log.log文件已经被创建了,大小为7kb,写入内容如下:

我们在次执行代码,可以发现创建了一个新的文件app-log.log.1存储旧的日志,而超过大小限制无法写入的内容写入到了app-log.log中。

不难发现,在写入到139行之后超过了10kb的限制,所以将这些内容备份到app-log.log.1中,将未写入到61行日志写入到了app-log.log

注意:

由于我们限制了备份到日志数量,所以当达到最大限制后,新的日志会覆盖旧的日志。

file appender还可以配置compresstrue,如果这样配置的话会将备份文件进行gzip压缩,也就是缓存文件会被压缩为app-log.log.1.gz,其他的行为是一致的就不演示了。

fileSync

用法与file基本是一致的,就不演示了,主要区别如下:

  • fileSync写入文件是同步的,而file是异步的
  • fileSync不支持配置compress将日志文件进行gzip压缩

还有multiFile可以动态写入多个文件,有兴趣可以自行学习一下,这里不说了。

dataFile

更常用的追加器来了,dataFile也是一个文件追加器,它根据可配置的时间(而不是文件大小)滚动日志文件。

基础配置项:

  • type: dataFile
  • filename: 要写入的文件路径
  • pattern: 可选字符串,默认为yyyy-MM-dd,用来确定怎么分割滚动日志

先根据这几个基础的字段来看一下用法,由于默认是以天分割,不方便演示,所以我这边设置pattern为分钟。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import log4js from 'log4js'

log4js.configure({
appenders: {
dateFile: {
type: 'dateFile',
filename: 'app-log.log',
pattern: 'yyyy-MM-dd-hh:mm'
}
},
categories: {
default: {
appenders: ['dateFile'],
level: 'info'
}
}
})

const logger = log4js.getLogger()
logger.info('这是一条日志')

log4js.shutdown()

执行代码可发现生成了日志文件

过了一分钟,我们再次执行代码观察日志文件:

可以得出我们写入日志时,会生成app-log.log文件。一分钟后的再次写入发生时,会将app-log.log重命名为app-log.log.yyyy-MM-dd-hh-mm然后创建一个新的app-log.log文件写入内容。

文件命名与时间分割都由pattern决定。默认是以天分割。

还有一些其他的配置项,比较容易看懂,就不做代码示例了:

  • compress : 是否使用gzip压缩,默认为false
    • keepFileExt: 布尔值,默认为false, 如果为true,则备份的日志文件将被命名为app-log.yyyy-MM-dd.log,而不是app-log.log.yyyy-MM-dd
    • fileNameSep: 备份文件命名的切割符,默认是.
    • numBackups: 保存旧文件的数量,默认是1

filedateFile等文件写入的appender,都可以配置mode、 encodeing、 flags等,一般情况下默认值即可。默认值是0o600 、 utf-8、'a'。也就是读写权限,utf8编码,追加模式

关于appenders的内容就到这里了,还有更多tcpappender去文档查看吧。

类别categories

类别是日志事件的组,至少要定义一个类别。

类别可以说的不多,前边的示例中演示过了,只说一个比较特别的功能类别继承

以官方的代码示例:

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
import log4js from 'log4js'
log4js.configure({
appenders: {
console: { type: "console" },
app: { type: "file", filename: "application.log" },
},
categories: {
default: { appenders: ["console"], level: "trace" },
catA: { appenders: ["console"], level: "error" },
"catA.catB": { appenders: ["app"], level: "trace" },
},
});

const loggerA = log4js.getLogger("catA");
loggerA.error("以error等级输出日志到控制台");
// catA的level为error,这个事件会被忽略
loggerA.trace("不会有日志输出");

const loggerAB = log4js.getLogger("catA.catB");
loggerAB.error(
"日志以error级别同时输出到控制台并写入文件"
);
loggerAB.trace(
"日志以trace级别同时输出到控制台并写入文件"
);

可以发现,loggerAB使用的类别为catA.catB,即便catA的级别为errorloggerAB.trace同样可以同时应用两种appender

布局layout

前边关于布局layout的内容,我们都省略掉了,其实是有原因的,log4js给各种appender默认添加了合适的布局,所以一般来说我们是不需要额外定义布局的。

布局的配置方式根appender是比较像的,log4js内置了几种layout,根据type指定layout,根据不同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
import log4js from 'log4js'

log4js.configure({
appenders: {
out: {
type: 'stdout'
},
outWithLayout: {
type: 'stdout',
layout: {
type: 'basic'
}
}
},
categories: {
default: {
appenders: ['out'],
level:'all'
},
layout: {
appenders: ['outWithLayout'],
level: 'all'
}
}
})

const logger = log4js.getLogger()
logger.info('默认stdout输出日志')
const layoutLogger = log4js.getLogger('layout')
layoutLogger.info('使用basic布局的日志输出')

输出内容:

可以发现,basic布局是没有颜色的。输出内容是时间戳、级别、类别,后跟格式化的日志事件数据。而我们默认的布局是有颜色的,用到的是colored

由于我们一般不需要对布局进行修改,所以这里仅仅列一下log4js的内置布局:

  • basic
  • coloredcoloured
  • messagePassThrough
  • dummy
  • pattern

可以自定义布局,如果有这个需求可以查看文档学习

作者

胡兆磊

发布于

2023-04-19

更新于

2023-04-20

许可协议