使用debug包替换console.log优雅的输出日志文件

2,289 阅读5分钟

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

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

debug基本用法

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

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

详情请看gitlab介绍

自定义输出

通过debug.log方法自定义输出,先上完整代码,再详细说明:

/**
 * 重定义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 (调试信息):记录系统用于调试的一切信息,内容或者是一些关键数据内容的输出。

如:

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

字符过滤

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

//原内容
[ '%cmain %c空数组:%c +2ms',
  'color: #6600FF',
  'color: inherit',
  'color: #6600FF',
  'array',
  [] 
]

过滤如下

  // 字符过滤--去掉颜色表识符%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对象获得调用信息

let fnCallArr = new Error().stack.match(/at (.*?) /g)
//原信息如下,是一个数组:
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__ "
//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 "

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

//过滤数组项
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

输出文件显示

第一次在掘金发帖,有不足的的欢迎指教,如果喜欢的记得点赞哦~~