0%

2022-03-15-带着问题看源码6-NodeRed中的日志模块

NodeRed 日志模块是基础的模块,负责运行期的信息打印,支持日志等级,同时提供了扩展机制,支持远程打印,打印另存等功能

1. 日志系统的意义#

让我们回到计算机世界的远古时期或者我们刚刚接触计算机世界的时期,那个时候我们有两种调试程序的办法:1)单步调试,一步步地跟踪,查看代码中变量的值。2) 是 printf 大法 —— 在特定的地方打印日志, 通过日志的输出,帮助快速定位。
单步调试方法费时费力,但能准确定位问题。printf 大法简单粗暴,需要尝试,大部分情况能快速找到问题。单步调试和 printf 方法搭配使用,相得益彰。但是单步调试止步于 gdb 等调试工具,而 printf 大法最终发展出了一系列的日志系统。原因就在于单步调试在程序员调试才能用,而 printf 大法可以在调试和生产线上都能用,并且输出的日志被各方面的人利用和解读。

printf 大法是很简陋的。在调试过程中,有可能日志打到很细粒度,比如每条数据的第三个字段是什么都打印出来了,但是真正运行又要把这些细粒度的日志删除。等到下次调试,我们又要知道每条数据的第三个字段是什么了。为此,我们希望日志打印是智能:调试或者线上出问题的时候,各种细粒度的日志全部打印出来,正常运行的时候输出一些最简单的信息就可以了。

针对这个问题,日志系统引入日志级别 (Level) 的办法解决。引入日志级别的概念之后,我们编程时打印日志,需要指明这条日志的级别。由于日志级别是最重要的参数,现在的日志系统都是直接通过使用不同的函数来指明级别的,包括 logger.TRACE, logger.DEGUB, logger.INFO, logger.WARN, logger.ERROR, logger.FATAL。其中级别的对比是 TRACE < DEBUG < INFO <WARN < ERROR < FATAL。同时系统运行行,我们将设定 log level 设置在某一个级别上,那么级别优先级高的 log 都能打印出来,低的都不能打印出。例如,如果设置优先级为 WARN,那么 FATAL、ERROR、WARN 级别的 log 能正常输出,而 INFO、DEBUG、TRACE 级别的 log 则会被忽略。

有了 Level, 我们可以随心意写点 log 了,只要控制好日志级别就行。默认情况下,我们的日志是打印到 Console 里,我们直接人眼看。随着时间的推移,情况变得复杂起来。比如我们需要将日志打入文件里,方便以后查看。即使日志打到文件,我们也需要登录到机器才能查看,我们需要在发生错误时收到邮件或者短信。为了满足这些需求,我们在日志系统中加入 Appender 部件。Appender 部件负责将日志写的不同的目的。

我们编程时 DEBUG 或者 TRACE 级别打出细粒度的信息,比如每条数据的样子。当我们调试时或者线上有问题时,我们将程序的当前日志级别设置成 TRACE 或者 DEBUG,从而将细粒度的信息打出来。而正常运行时,我们将当前日志级别设置成 INFO 或者 WARN 级别,从而忽略细粒度的信息,降低 IO 操作和提升系统性能。

有了日志的 Level 和 Appender, 我们还需要解决日志样式的问题。一般情况,我们希望的日志格式包括:Level, 函数名,文件名和打日志的代码行数。这可以通过日志系统的 Formatter 组件来实现的。

2. NodeRed 日志模块功能#

以配置文件方式,灵活配置日志的处理者,通过调整日志等级,可打印不同级别的信息。

3. NodeRed 中日志模块及参与者#

日志模块主要参与者:

  1. 功能文件:
    packages/node_modules/@node-red/util/lib/log.js
  2. 测试文件:
    test/unit/@node-red/util/lib/log_spec.js
  3. 配置文件:
    setting.json
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
48
49
50
...
// Configure the logging output
logging: {
// Only console logging is currently supported
console: {
// Level of logging to be recorded. Options are:
// fatal - only those errors which make the application unusable should be recorded
// error - record errors which are deemed fatal for a particular request + fatal errors
// warn - record problems which are non fatal + errors + fatal errors
// info - record information about the general running of the application + warn + error + fatal errors
// debug - record information which is more verbose than info + info + warn + error + fatal errors
// trace - record very detailed logging + debug + info + warn + error + fatal errors
// off - turn off all logging (doesn't affect metrics or audit)
level: "info",
// Whether or not to include metric events in the log output
metrics: false,
// Whether or not to include audit events in the log output
audit: false
},
logstash: {
level:'info',
metrics:false,
handler: function(conf) {
var net = require('net');
var logHost = '127.0.0.1',logPort = 8124;
var conn = new net.Socket();
conn.connect(logPort,logHost)
.on('connect',function() {
console.log("Logger connected")
})
.on('error', function(err) {
// Should attempt to reconnect in a real env
// This example just exits...
process.exit(1);
});
// Return the function that will do the actual logging
return function(msg) {
var message = {
'@tags': ['node-red', 'test'],
'@fields': msg,
'@timestamp': (new Date(msg.timestamp)).toISOString()
}
try {
conn.write(JSON.stringify(message)+"\n");
}catch(err) { console.log(err);}
}
}
}
},
...
  1. 使用者:
    加载 var util = require(“util”), 调用 util.log。

4. NodeRed 为什么这么设计,这种设计的优劣有哪些#

  1. 灵活
    以配置文件配置日志等级,采用分级机制处理数据,可在程序发布、测试期看到不同的信息

  2. 扩展性强
    可自定义日志处理函数,可发送到服务器、存储到指定位置,

  3. 易用
    对外只提供了 Until.log 一个接口,不需要代码控制日志等级

5. 应用场景分析#

  1. 日志的本地存储
  2. 日志的远端实时查看

6. 实践#

6.1. 服务端代码#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  console.log('client connected');
c.on('end', () => {
console.log('client disconnected');
});
c.on('data', (data)=>{
console.log(data.toString('utf8'));
})
c.write('hello\r\n');
c.pipe(c);
});
server.on('error', (err) => {0
throw err;
});
server.listen(8124, () => {
console.log('server bound');
});

6.2. NodeRed 配置 settings.js#

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
48
49
50
...
// Configure the logging output
logging: {
// Only console logging is currently supported
console: {
// Level of logging to be recorded. Options are:
// fatal - only those errors which make the application unusable should be recorded
// error - record errors which are deemed fatal for a particular request + fatal errors
// warn - record problems which are non fatal + errors + fatal errors
// info - record information about the general running of the application + warn + error + fatal errors
// debug - record information which is more verbose than info + info + warn + error + fatal errors
// trace - record very detailed logging + debug + info + warn + error + fatal errors
// off - turn off all logging (doesn't affect metrics or audit)
level: "info",
// Whether or not to include metric events in the log output
metrics: false,
// Whether or not to include audit events in the log output
audit: false
},
logstash: {
level:'info',
metrics:false,
handler: function(conf) {
var net = require('net');
var logHost = '127.0.0.1',logPort = 8124;
var conn = new net.Socket();
conn.connect(logPort,logHost)
.on('connect',function() {
console.log("Logger connected")
})
.on('error', function(err) {
// Should attempt to reconnect in a real env
// This example just exits...
process.exit(1);
});
// Return the function that will do the actual logging
return function(msg) {
var message = {
'@tags': ['node-red', 'test'],
'@fields': msg,
'@timestamp': (new Date(msg.timestamp)).toISOString()
}
try {
conn.write(JSON.stringify(message)+"\n");
}catch(err) { console.log(err);}
}
}
}
},
...

6.3. 服务端信息#

截图_选择区域_20220314145740