青训营笔记 | 前端监控系统相关知识

56 阅读2分钟

错误数据采集

资源加载错误

使用 addEventListener() 监听 error 事件,可以捕获到资源加载失败错误。

// 捕获资源加载失败错误 js css img...
window.addEventListener('error', e => {
    const target = e.target
    if (!target) return

    if (target.src || target.href) {
        const url = target.src || target.href
        lazyReportCache({
            url,
            type: 'error',
            subType: 'resource',
            startTime: e.timeStamp,
            html: target.outerHTML,
            resourceType: target.tagName,
            paths: e.path.map(item => item.tagName).filter(Boolean),
            pageURL: getPageURL(),
        })
    }
}, true)

js 错误

使用 window.onerror 可以监听 js 错误。

// 监听 js 错误
window.onerror = (msg, url, line, column, error) => {
    lazyReportCache({
        msg,
        line,
        column,
        error: error.stack,
        subType: 'js',
        pageURL: url,
        type: 'error',
        startTime: performance.now(),
    })
}

promise 错误

使用 addEventListener() 监听 unhandledrejection 事件,可以捕获到未处理的 promise 错误。

// 监听 promise 错误 缺点是获取不到列数据
window.addEventListener('unhandledrejection', e => {
    lazyReportCache({
        reason: e.reason?.stack,
        subType: 'promise',
        type: 'error',
        startTime: e.timeStamp,
        pageURL: getPageURL(),
    })
})

sourcemap

一般生产环境的代码都是经过压缩的,并且生产环境不会把 sourcemap 文件上传。所以生产环境上的代码报错信息是很难读的。因此,我们可以利用 source-map 来对这些压缩过的代码报错信息进行还原。

当代码报错时,我们可以获取到对应的文件名、行数、列数:

{
    line: 1,
    column: 17,
    file: 'https:/www.xxx.com/bundlejs',
}

然后调用下面的代码进行还原:

async function parse(error) {
    const mapObj = JSON.parse(getMapFileContent(error.url))
    const consumer = await new sourceMap.SourceMapConsumer(mapObj)
    // 将 webpack://source-map-demo/./src/index.js 文件中的 ./ 去掉
    const sources = mapObj.sources.map(item => format(item))
    // 根据压缩后的报错信息得出未压缩前的报错行列数和源码文件
    const originalInfo = consumer.originalPositionFor({ line: error.line, column: error.column })
    // sourcesContent 中包含了各个文件的未压缩前的源码,根据文件名找出对应的源码
    const originalFileContent = mapObj.sourcesContent[sources.indexOf(originalInfo.source)]
    return {
        file: originalInfo.source,
        content: originalFileContent,
        line: originalInfo.line,
        column: originalInfo.column,
        msg: error.msg,
        error: error.error
    }
}

function format(item) {
    return item.replace(/(./)*/g, '')
}

function getMapFileContent(url) {
    return fs.readFileSync(path.resolve(__dirname, `./maps/${url.split('/').pop()}.map`), 'utf-8')
}

每次项目打包时,如果开启了 sourcemap,那么每一个 js 文件都会有一个对应的 map 文件。

bundle.js
bundle.js.map

这时 js 文件放在静态服务器上供用户访问,map 文件存储在服务器,用于还原错误信息。source-map 库可以根据压缩过的代码报错信息还原出未压缩前的代码报错信息。例如压缩后报错位置为 1 行 47 列,还原后真正的位置可能为 4 行 10 列。除了位置信息,还可以获取到源码原文。