Libon

Level Logger

3Mins #JavaScript
如何设计分级日志打印器

ToC

需求

需求是构建一个可以传入预定义好类型级别,得到一个新的函数或对象,可以通过调用其上面的方法来生成当前对象上允许的级别日志。日志级别共分为 4 个级别,分别为:

  1. silent:静默执行,不会输出任何级别的日志
  2. error:只会输出 error 级别的日志
  3. warn:可以输出 error、warn 级别的日志
  4. info:可以输出 error、warn、info 等任何类型的日志

举个例子,比如传入了 warn,那么调用其上面的 info 方法则不会有任何输出,但是 error warn 方法依旧可以输出对应的日志。

这种输入决定输出类型的函数通常会使用使用工厂模式来处理。我们先了解一下工厂模式的实现思路,其实很多设计模式都是听起来很牛x,但实际上这些模式都是我们日常开发中一些很普遍的做法,我们按照需求一步一步去做就有了以下代码:

1
function createLogger(level) {
2
function error(msg) {
3
console.error(`[error] ${msg}`)
4
}
5
6
function warn(msg) {
7
console.warn(`[warn] ${msg}`)
8
}
9
10
function info(msg) {
32 collapsed lines
11
console.info(`[info] ${msg}`)
12
}
13
14
let logger = {}
15
if (level === 'silent') {
16
logger = {
17
error: () => {},
18
warn: () => {},
19
info: () => {},
20
}
21
} else if (level === 'error') {
22
logger = {
23
error,
24
warn: () => {},
25
info: () => {},
26
}
27
} else if (level === 'warn') {
28
logger = {
29
error,
30
warn,
31
info: () => {},
32
}
33
} else if (level === 'info') {
34
logger = {
35
error,
36
warn,
37
info,
38
}
39
}
40
41
return logger
42
}

日志的权重

以上虽然可以实现需求,但这对我们的学习毫无帮助,因为这只不过是将每天都在写的代码又重复了一遍。后来我发现了另一种思路,即使用一个对象来存储日志的级别,用对象的 key 来保存日志的级别,用对象的 value 来保存日志的权重,这样我们就可以通过权重来判断日志是否可以输出了。这样我们就可以重构一下代码:

1
const LEVELS = {
2
silent: 0,
3
error: 1,
4
warn: 2,
5
info: 3,
6
}
7
8
function createLogger(level) {
9
const thresh = LogLevels[level]
10
14 collapsed lines
11
function print(method) {
12
if (thresh < LEVELS[level]) {
13
return () => {}
14
}
15
16
return (msg) => console[method](`[${method}] ${msg}`)
17
}
18
19
return {
20
error: print.bind(null, 'error'),
21
warn: print.bind(null, 'warn'),
22
info: print.bind(null, 'info'),
23
}
24
}

只不过在使用的时候还是需要判断一下运行环境,比如说在 “TTY”( Teletypes 或 teletypewriters:虚拟控制台)和服务器 CI 环境中就需要谨慎操作了,因为通常这些日志都是需要保留用于排查错误,或是查看系统当前状态的,如果贸然清除打印的日志的话,则会使得出现问题是排查十分困难,所以建议在调用前判断一下 process.stdout.isTTY && !process.env.CI,如果结果为 true,则表示可以安全调用,否则还是使用一个空函数来替代较为稳妥。

以上。


CD ..
回顾上一篇
从状态机到 xstate