阅读 627

前端错误监控

错误捕获

onerror

onerror可以捕获到宏任务抛出的错误,微任务比如:Promise,和async函数抛出的错误是捕获不到的。


window.onerror = (msg) => {
  console.log('捕获到错误', msg)
}

// 可以捕获到错误
throw new Error()

// 可以捕获到错误
setTimeout(() => {
  throw new Error()
})

async function fn () {
  // 捕获不到这个错误
  throw new Error()
}
fn()

// 捕获不到这个错误
new Promise(() => {
  throw new Error()
})
复制代码

script error

对于本域的js抛出的错误,onerror包含了详情的错误信息。对于其他域的js抛出的错误,只会在msg中显示简单的 Script error


window.onerror = (msg, url, line, column, error) => {
  console.log(
    `
      捕获到错误
      ${msg},
      ${url},
      ${line},
      ${column},
      ${JSON.stringify(error)},
    `
  )
}
复制代码

本域的js抛出的错误.png

其他域的js抛出的错误.png

如何解决这个问题
  1. 在script标签上添加,crossorigin属性
  2. 静态文件服务器开启CORS

我们就可以获得其他域js抛出错误的详细信息了。

onunhandledrejection

onunhandledrejection 可以捕获未处理的Promise中抛出的错误。

但是onunhandledrejection这个方法并不是很好,因为错误缺乏很多有用的信息,比如错误的行数和列数


window.onunhandledrejection = () => {
  console.log('捕获到错误')
}


async function fn () {
  // 可以捕获到这个错误
  throw new Error()
}
fn()

// 可以捕获到
new Promise(() => {
  throw new Error()
})
复制代码

try…catch

try…catch 捕获不到Promise中抛出的错误,以及异步的错误。但是对于 async…await,中异步错误,try…catch 可以捕获(可能和await相关,await使其同步化了?🤔️)

try {
  setTimeout(() => {
    throw new Error()
  })
} catch (error) {
  // 无法捕获错误
}

try {
  new Promise(() => {
    throw new Error()
  })
} catch (error) {
  // 无法捕获错误
}

async function fn () {
  try {
    await new Promise(() => {
      throw new Error()
    })
  } catch (error) {
    // 可以捕获到错误
  }
}
复制代码

格式化错误信息

我们的目标是搭建一个生产环境错误信息收集平台,用户使用的浏览器各有不同,错误对象的格式不同浏览器之间可能有所不同,所以我们需要对不同格式的错误信息,进行标准化。

github已经有了现成的轮子,TraceKit - Cross browser stack traces.

TraceKit安装和使用


npm i tracekit
复制代码

import TraceKit from 'tracekit'

TraceKit.report.subscribe((errorReport) => {
  // 在这里可以通过ajax进行数据上报
  console.log(errorReport)
})

window.onerror = (m, s, l, c, error) => {
  TraceKit.report(error)
}
复制代码

错误定位

SourceMap

Source Map 解决代码压缩后,错误无法在源码中定位的问题

SourceMap的格式


{
  "version": 3, // SourceMap的版本
  "sources": [ // 转换前的文件来源
    "webpack:///webpack/universalModuleDefinition",
    "webpack:///webpack/bootstrap"
  ],
  "names": [], // 转换前的变量名
  "mappings": "AAAA,AACA;AACA", // 位置信息
  "file": "react-ui-components-library.js", // 转换后的文件名
  "sourceRoot": "" // 转换前的文件目录,
  "sourcesContent":[] // 原始文件内容
}
复制代码

SourceMap是如何通过mappings属性提供的位置信息找到源码的,可以查看阮一峰老师的博客,JavaScript Source Map 详解

错误上报

数据上报通常使用ajax的方式,我们可以配合requestIdleCallback(注意兼容性),在浏览器空闲时进行数据上报

import TraceKit from 'tracekit'

const errors = []
let isReporting = false


// TraceKit会自动监听onerror,onunhandledrejection等事件
TraceKit.report.subscribe((errorReport) => {
  errors.push(errorReport)
  report()
})

// 数据上报,使用requestIdleCallback,可以避免主线程的阻塞
const report = () => {
  if (isReporting) {
     return
  }
  isReporting = true
  requestIdleCallback(callback)
}
const callback = (deadline) => {
  isReporting = false
  while (deadline.timeRemaining() > 0 && datas.length > 0) {
    // 数据上报
    http(datas.pop())
  }
  if (datas.length) {
    report()
  }
}
复制代码

错误平台搭建

在内网的测试环境,我们可以将js文件和map文件一起发布,问题不大。但是对于外网的生产环境,如果将map文件一并发布,他人是很容易拿到项目源码的。

来自https://github.com/joeyguo/blog/issues/14

我们可以将项目打包产生的map文件,单独发布到公司的内网环境。然后将外网环境报告的错误与内网的map文件配合通过mozilla/source-map,快速定位到源码哪里出现了错误。 å

这里我基于上面的架构写的一个demo,包含了错误收集以及错误的定位

image.png

image.png

参考文章和项目