Source Map 在前端监控中的应用和实践
Web 前端开发中,对于生产环境的代码通常会进行压缩和混淆处理,以减小代码体积并提高源代码安全性。然而当生产环境有 JS 错误发生时,压缩和混淆的代码也让定位错误变得更困难。
我们先来看一个爱奇艺智能前端异常监控平台(鹰眼 Hawkeye)检测到的错误日志:
![](https://static001.geekbang.org/infoq/bd/bd35746d0278e8fd2d81e3671f67811b.png)
图一 Hawkeye 收集的 JS 错误示例
可以看到,压缩后的错误行号和列号已经丢失,函数名也经过了变换,出错的真实文件名很难确定。如此,根据压缩代码的报错信息是很难定位错误的,但是,线上代码又必须压缩上传处理。
如何平衡?SourceMap 正是解决这个矛盾的利器。本文将从 Source Map 的基本概念及其在前端异常监控中的运用、设计及实践等方面进行介绍。
一、Source Map 介绍
Source Map 是一种存储了源代码和编译后代码映射关系的信息文件,包含代码转换前后的位置信息。现在各种主流的打包工具都支持生成 Source Map,例如: Uglify JS、Grunt、Gulp、Webpack 等。生成 Source Map 一般在项目打包的编译阶段进行,打包工具先将源代码解析出 AST(Abstract Syntax Tree),在生成编译后的代码时,即图二 generate 过程中,利用 AST 中节点的来源信息等生成 Source Map。
![](https://static001.geekbang.org/infoq/51/51190662571d81aceb8fdde9b0de54b7.png)
图二 编译并生成 Source Map
二、JS 错误采集和利用 Source Map 解析错误
JS 错误一般可以通过 window.addEventListener 监听 error 事件收集,框架错误一般利用框架暴露的钩子函数收集,比如 Vue.config.errorHandler。收集到的错误中,从 Error.prototype.stack 可解析出错误堆栈信息。
获取到错误堆栈信息、Source Map 文件内容后则可以通过 Mozilla 的 source-map 库进行错误定位。该库中的 SourceMapConsumer 实例表示一个已解析的源映射的相关信息,我们可以通过为这个实例提供文件位置和文件内容来在生成的源中查询有关原始文件位置的信息。
目前,各大监控平台均有针对错误监控的 Source Map 的解析功能。例如开源监控平台 Sentry 支持 Webpack 插件和自身的 CLI 工具上传 map 文件后进行解析,付费平台 Fundebug 支持通过前端 UI、自身 CLI 及接口上传 map 文件后进行解析。但是这些平台也有一些缺点,比如有的不支持使用者选择文件进行关联匹配,有的在使用者选择 map 匹配不成功时场景交互体验不友好。我们受之启发,开发了基于鹰眼监控系统的 Source Map 功能,主要有以下优势:
支持更简单的配置来通过 Webpack 的 Plugin 进行 Source Map 文件的上传,并且上传过程出现异常不影响项目整体编译流程,代码侵入性低。
支持更多的上传和选择 Source Map 文件的方式,以适用于更多的业务错误监控的场景,且在使用过程中有更明确的提示和更友好的交互。
支持 map 文件管理功能,通过项目名称和版本号选择关联,新版本可以选择历史版本的 map 文件进行匹配。
下面对 Source Map 在监控系统中的运用、鹰眼 Source Map 功能设计及实践详细阐述。
三、Source Map 在监控系统的运用
将 Source Map 运用于前端异常监控系统,主要需要解决的问题便是使用者如何上传 Source Map 以及监控系统如何将 Source Map 和错误信息关联。考虑到易用性,鹰眼 SourceMap 功能支持三种使用方式:针对单个 JS 报错堆栈从手动上传 Source Map、集成插件自动上传关联 JS 错误堆栈信息、针对单个 JS 报错堆栈从 SourceMap 列表中选择关联。如图三所示。
![](https://static001.geekbang.org/infoq/ce/ce818623a36d542b91e1da9c03db45ab.png)
图三 Hawkeye Source Map 三大功能
相应的,整体架构如下:
![](https://static001.geekbang.org/infoq/e3/e35da58ff28aa5c9169619b6dee2dc5b.png)
图四 整体架构
以下是对各个模块的介绍:
Source Map 自动上传插件(支持 Webpack3+):使用者在项目打包流程中集成此插件,便可将生成的 Source Map 自动上传。此插件能够在不影响项目编译流程的前提下,在 Webpack 输出 asset 到 output 目录之后,获取打包生成的 map 文件并执行上传逻辑。并且可以通过参数配置可以选择在 Webpack 编译完成执行后对 map 文件进行删除,不影响上线流程。鉴于历史项目使用 Webpack 老版本较多,鹰眼针对 Webpack 不同的版本进行了兼容优化。
手动上传 Source Map 模块:使用者可针对单条 JS 错误,上传单个 Source Map 进行匹配,并且进行了同名检测的提示,在能够自助上传 map 外也能通过提示减少错误匹配的情况。
Source Map 自助选择模块:提供了可以通过 Source Map 管理中心进行选择 map 进行关联的功能,对于不能每次每个版本都能上传 Source Map 文件的项目,能够进行列表和搜索选择需要匹配的 map 文件。
Source Map 定位展示模块:展示 JS 报错对应的真实源代码及位置。
Source Map 管理中心:集中管理 Source Map 文件的添加删除。
上面提到的三种使用方式是互补的关系,其中集成插件自动上传关联 JS 错误堆栈信息的方式是非常建议使用者接入的,其优势在于可方便地集成到项目的自动化构建发布流程。接入一次之后,收到 JS 报错时,便可查看此报错在源代码的位置。实现流程如图五示:
SourceMap 自动上传插件拉取项目打包生成的 Source Map 文件。
调用文件上传中心接口上传文件。文件上传中心有独立的鉴权机制、对接了网络安全服务。
获取 SourceMap 文件存储信息后连同项目、版本提交至 Source Map 管理中心。提交完成后可以删除构建过程中产生的 Source Map 文件。
使用者在前端异常监控系统查看 JS 报错时,监控系统根据 JS 报错的项目、版本号从 SourceMap 管理中心获取 Source Map 文件。
调用 Mozillasource-map 库进行解析关联,对源代码信息进行可视化展示。
![](https://static001.geekbang.org/infoq/59/59503b6cadd2607df198807f2e2752e4.png)
图五 Hawkeye Source Map 自动上传插件流程设计
四、项目中接入
在项目中接入 Source Map 自动上传插件的方法及使用技巧如下:
初始化监控平台接入代码时需要携带 versionNum 字段,该字段为当前项目的版本号,以便于后期错误堆栈和项目 map 文件进行版本管理处理。
在项目打包时生成 map 文件(需要独立的 map 文件)。Webpack 对于 Source Map 的生成配置,建议模式使用 hidden-source-map,其能够生成完整独立的 map 文件且不会泄露 sourceMappingURL,但缺点是会造成编译耗时增加。cheap-source-map 和 cheap-module-source-map 因省略一些内容,并不会准确完整的还原错误位置。
打包部署项目时集成 map 上传插件, 在 devDependencies 中安装插件,在项目中的 Webpack 的 plugin 配置中增加该插件配置。
若接入监控平台时正确传入版本号和项目名称,并且在项目打包时通过 map 上传插件上传正确版本及项目名称的 map 文件。鹰眼在发生错误上报堆栈信息时会自动将 JS 名称和 map 文件进行匹配,显示其错误正确的源码地址。
若无法自动匹配时,也可以从 Source Map 管理中心选择该项目和版本对应的 map 文件进行匹配,或者手动上传 map 文件自行匹配。
在鹰眼监控后台的使用方式如图六标注示意:
![](https://static001.geekbang.org/infoq/5c/5c5172d338b70631e1340fc984991369.png)
图六 Hawkeye Source Map 功能接入示例
五、总结与展望
我们再看开篇提到的错误,在鹰眼管理系统中 Source Map 视图下,可以很方便地看到真实文件名和代码行号了,如图七。由于可以直接定位到 JS 出错的源代码位置,接入了此功能的项目,JS 错误定位排查效率得到了极大的提升。
![](https://static001.geekbang.org/infoq/1d/1d6faa1f8c535c926adc36a11758c386.png)
图七 鹰眼 JS 错误 SourceMap 视图
在一些项目生成 Source Map 以及集成上传 Source Map 插件的实践中,我们也遇到过一些难点:
1、在一些采用自定义 Webpack 配置打包的项目中,按照官方常规的 devtool:hidden-source-map 配置总会使得一些打包生成的 JS 文件体积增大,除了单独生成的 Source Map 文件,在一些编译后的 JS 中混入了一些 SourceMap 的内容。我们通过详细查看这些体积增大的 JS 文件的特点和内容,最终对 vue-loader、css-loader 分别指定 CSS 的 Source Map 选项关闭解决问题。
2、微前端项目的 Source Map 关联问题。在一些微前端架构项目中,微前端独立上线,有独立的版本控制,而采集报错的 Hawkeye JSSDK 只需集成在基座项目,微前端打包上线的流程中无法获取基座的版本,会造成 SourceMap 文件无法自动关联匹配。我们采取一种比较简洁的方式,在采集 JSSDK 中支持传入微前端版本的函数,在基座项目中根据微前端的标识等计算获取微前端的版本,当采集到报错投递时,将微前端的版本一同投递。这样后台关联时,优先匹配微前端版本号,其次匹配基座版本号,即可成功自动关联。
3、因为准确的定位错误需要上传完整的独立 map 文件,在个别较大的项目中,打包生成 Source Map 文件时,会影响编译速度,比较坏的情况下会增加 80%的编译时间。所以后续考虑根据这些项目自身情况,独立封装和优化 SourceMap 生成模块,而不是通过指定打包工具的选项执行其内置生成流程,尽量减少对项目整体打包耗时的影响。
另外,根据接入项目的情况,鹰眼 Source Map 自动上传插件后续也会支持更多打包构建工具,比如 rollup、gulp。
版权声明: 本文为 InfoQ 作者【爱奇艺技术产品团队】的原创文章。
原文链接:【http://xie.infoq.cn/article/424b74143f44abb9f88c306f5】。文章转载请联系作者。
评论