如何设计一个日志打印器
需求
需求是构建一个可以传入预定义好类型级别,得到一个新的函数或对象,可以通过调用其上面的方法来生成当前对象上允许的级别日志。日志级别共分为 4 个级别,分别为:
- silent:静默执行,不会输出任何级别的日志
- error:只会输出 error 级别的日志
- warn:可以输出 error、warn 级别的日志
- info:可以输出 error、warn、info 等任何类型的日志
举个例子,比如传入了 warn
,那么调用其上面的 info
方法则不会有任何输出,但是 error
warn
方法依旧可以输出对应的日志。
这种输入决定输出类型的函数通常会使用使用工厂模式来处理。我们先了解一下工厂模式的实现思路,其实很多设计模式都是听起来很牛x,但实际上这些模式都是我们日常开发中一些很普遍的做法,我们按照需求一步一步去做就有了以下代码:
function createLogger(level) {
function error(msg) {
console.error(`[error] ${msg}`)
}
function warn(msg) {
console.warn(`[warn] ${msg}`)
}
function info(msg) {
console.info(`[info] ${msg}`)
}
let logger = {}
if (level === 'silent') {
logger = {
error: () => {},
warn: () => {},
info: () => {},
}
} else if (level === 'error') {
logger = {
error,
warn: () => {},
info: () => {},
}
} else if (level === 'warn') {
logger = {
error,
warn,
info: () => {},
}
} else if (level === 'info') {
logger = {
error,
warn,
info,
}
}
return logger
}
日志的权重
以上虽然可以实现需求,但这对我们的学习毫无帮助,因为这只不过是将每天都在写的代码又重复了一遍。后来我发现了另一种思路,即使用一个对象来存储日志的级别,用对象的 key 来保存日志的级别,用对象的 value 来保存日志的权重,这样我们就可以通过权重来判断日志是否可以输出了。这样我们就可以重构一下代码:
const LEVELS = {
silent: 0,
error: 1,
warn: 2,
info: 3,
}
function createLogger(level) {
const thresh = LogLevels[level]
function print(method) {
if (thresh < LEVELS[level]) {
return () => {}
}
return (msg) => console[method](`[${method}] ${msg}`)
}
return {
error: print.bind(null, 'error'),
warn: print.bind(null, 'warn'),
info: print.bind(null, 'info'),
}
}
只不过在使用的时候还是需要判断一下运行环境,比如说在 "TTY"(
Teletypes 或 teletypewriters:虚拟控制台)和服务器 CI 环境中就需要谨慎操作了,因为通常这些日志都是需要保留用于排查错误,或是查看系统当前状态的,如果贸然清除打印的日志的话,则会使得出现问题是排查十分困难,所以建议在调用前判断一下 process.stdout.isTTY && !process.env.CI
,如果结果为 true,则表示可以安全调用,否则还是使用一个空函数来替代较为稳妥。
以上。