image-20191005085442087

debug是开发过程的重要一环,趁着国庆放假,正好优化一下debug工具,下面对debug的输出进行自定义,并实现以下功能:

  • 同时输出到console和log文件,方便软件打包后的日志记录。
  • 显示函数调用链,方便定位问题来源
  • 展开显示数组和对象
  • 实现日志等级的高亮显示,如:INFO、ERROR等

debug基本用法

debug包是被大量使用的开发基础包,有上千万的下载量,支持定义命名空间,并通过命名空间控制日志的输出,很好的弥补了console.log的不足。

1
2
3
import debug from 'debug' //模块中引入
const log = debug('home') //定义命名空间,后面使用log将自动以home开头
log('name', name) //括号中支持任意多个参数,将输出:home name 小明

详情请看gitlab介绍

自定义输出

通过debug.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* 重定义debug输出, 可同时输出到终端和文件
*/
debug.log = (...args) => {
//* 输出到console
if (args[0].includes('ERROR')) {
console.error(...args) // 浏览器console error突出显示
} else {
console.log(...args) // 一般信息
}

//* 输出到文件

// 字符过滤--去掉颜色表识符%c
let headInfo = args[0].split('%c').join('') + ' '
// 过滤多余的color参数
args = args.filter(item => {
if (!item || typeof item !== 'string') {
return true // 避免下面空值报错
} else {
return item.substring(0, 6) !== 'color:'
}
})

//空值显示处理
let restInfo = ''
if (args.length > 1) {
restInfo = args
.slice(1)
.map(item => {
if (item === undefined) return 'undefined'
if (item === null) return 'null'
if (item === []) return item
if (item === {}) return item
// eslint-disable-next-line prettier/prettier
if (typeof item === 'string') return ' \'' + item + '\''
if (typeof item === 'object') return JSON.stringify(item, null, 2) //util.inspect(item, true, 3) //数组和对象格式化输出
return item
})
.join(' ')
}

//获取函数调用信息
let fnCallArr = new Error().stack.match(/at (.*?) /g)
//过滤意义不大的数组项
let fnCallInfo = fnCallArr
.slice(2)
.filter(item => {
if (['webpack_require', 'node_modules', 'invokeWithErrorHandling', 'updateComponent'].some(i => item.includes(i))) return false
return true
})
.join('🠐 ')

//过滤用处不大的字符
;[
'at ',
'VueComponent.',
'?vue&type=script&lang=js& ',
'Module../src/',
' 🠐 callHook 🠐 Object.insert 🠐 invokeInsertHook 🠐 Vue.patch 🠐 Vue._update',
' 🠐 eval 🠐 Array.reduce 🠐 Proxy.render 🠐 Vue._render',
].forEach(item => (fnCallInfo = fnCallInfo.split(item).join('')))
console.log('---------fnCallArr:', fnCallArr)

//空格填充对齐, 至少距左侧45个字符(不含日期), 更多的每10个一位
let info = headInfo + restInfo
//字符数计算,中文2个,英文1个
let infoLen = 0
if (info.includes('\n')) {
//有换行时取最后一行,并补充空格57
info = info + ' '
infoLen = getByteLen(info.split('\n').pop()) //字符计数,中文2个,英文1个
} else infoLen = getByteLen(info)

//生成需补充的空格, 函数调用信息显示的开始位置为 22(日期) + 45(最小位置), 下面是未考虑日期的, 因此以45为基准
let space = infoLen < 45 ? ' '.substring(infoLen) : ' '.substring((infoLen - 45) % 10)
console.log('infoLen', infoLen, 'space', space.length)
info = info + space + '# ' + fnCallInfo

// 日期时间
let d = thisTime('all') //自己写的当前日期时间生成函数, 输出格式: 20190420 06:20:54.513

//添加到文件
fs.appendFileSync(DB_PATH + '/log.log', d + ' ' + info + '\n')

//fs.appendFileSync(DB_PATH + '/log.log', d + ' ' + '@ infoLen ' + infoLen + ' space ' + space.length + '\n')
// fs.appendFileSync(DB_PATH + '/log.log', d + ' ' + util.inspect(args) + '\n') //原字符

}

下面分别说明一下

日志等级

日志一般分以下等级,不过个人开发一般只用到INFO和ERROR,直接用vscode查看日志,在日志中使用下面大写的符号即可高亮显示。

FATAL(致命错误):记录系统中出现的能使用系统完全失去功能,服务停止,系统崩溃等使系统无法继续运行下去的错误。例如,数据库无法连接,系统出现死循环。

ERROR(一般错误):记录系统中出现的导致系统不稳定,部分功能出现混乱或部分功能失效一类的错误。例如,数据字段为空,数据操作不可完成,操作出现异常等。

WARN(警告):记录系统中不影响系统继续运行,但不符合系统运行正常条件,有可能引起系统错误的信息。例如,记录内容为空,数据内容不正确等。

INFO(一般信息):记录系统运行中应该让用户知道的基本信息。例如,服务开始运行,功能已经开户等。

DEBUG (调试信息):记录系统用于调试的一切信息,内容或者是一些关键数据内容的输出。

如:

1
2
log('name', name) //括号中支持任意多个参数,
log('ERROR', 'name', name) //需要区分等级的,将等级放在第一个,方便区分显示

字符过滤

原输出内容如下,是给终端显示的,带有颜色定义的字符,当输出到文件时则用处不大,需要过滤掉。

1
2
3
4
5
6
7
8
//原内容
[ '%cmain %c空数组:%c +2ms',
'color: #6600FF',
'color: inherit',
'color: #6600FF',
'array',
[]
]

过滤如下

1
2
3
4
5
6
7
8
9
10
// 字符过滤--去掉颜色表识符%c
let headInfo = args[0].split('%c').join('') + ' '
// 过滤多余的color参数
args = args.filter(item => {
if (!item || typeof item !== 'string') {
return true // 避免下面空值报错
} else {
return item.substring(0, 6) !== 'color:'
}
})

函数调用链

通过error对象获得调用信息

1
let fnCallArr = new Error().stack.match(/at (.*?) /g)
1
2
3
4
5
6
7
8
9
//原信息如下,是一个数组:
0: "at Function.debug__WEBPACK_IMPORTED_MODULE_8___default.a.log "
1: "at debug "
2: "at eval "
3: "at Module../src/main.js "
4: "at __webpack_require__ "
5: "at fn "
6: "at Object.0 "
7: "at __webpack_require__ "
1
2
3
4
5
6
7
8
9
10
11
//vue组件调用
0: "at Function.debug__WEBPACK_IMPORTED_MODULE_8___default.a.log "
1: "at debug "
2: "at VueComponent.mounted "
3: "at invokeWithErrorHandling "
4: "at callHook "
5: "at Object.insert "
6: "at invokeInsertHook "
7: "at Vue.patch "
8: "at Vue._update "
9: "at Vue.updateComponent "

信息比较冗杂,需要过滤一下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//过滤数组项
arr[0]、arr[1]
__webpack_require__
node_modules
invokeWithErrorHandling
updateComponent

//过滤字符
at
VueComponent.
?vue&type=script&lang=js&
Module../src/
🠐 callHook 🠐 Object.insert 🠐 invokeInsertHook 🠐 Vue.patch 🠐 Vue._update
🠐 eval 🠐 Array.reduce 🠐 Proxy.render 🠐 Vue._render

输出文件显示

image-20191004182707074

测试

1
2
3
4
5
6
7
8
9
10
log('ERROR', 'aaa')
log('null:', null)
log('undefined:', undefined)
log('空字符:', '')
log('空字符:', 'string', '')
log('空数组:', [])
log('空数组:', 'array', [])
log('空对象:', 'object', {})
log('true:', true)
log('type', [1, true, Symbol('foo').toStirng(), null, 2, undefined, Infinity, NaN, { a: 'b' }].join(' '))