蚂蚁金服-基于数据和堆栈映射快速定位前端问题

11,082 阅读25分钟

前端早早聊大会,前端成长的新起点,与掘金联合举办。 加微信 codingdreamer 进大会专属内推群,赢在新的起跑线。


第十四届|前端成长晋升专场,8-29 即将直播,9 位讲师(蚂蚁金服/税友等),点我上车👉 (报名地址):


本文为第五届 - 前端监控体系建设专场讲师,来自蚂蚁金服的 亚瑟 的分享 - 讲稿简要整理版(包含功能演示的完整版请看录播视频和 PPT):

前言

大家好,欢迎大家今天来参加监控场的分享,我是来自蚂蚁金服 - 体验技术部的亚瑟,我们的团队目前致力于前端基础设施的建设,监控做为前端基础设施的一部分,自然也是我们的主要的业务之一。

相比于服务端,前端在监控这方面起步较晚,想必很多企业在前端监控这一块也是在摸索阶段,那么今天就来为大家分享一下我们团队在前端监控方面的一些实践与经验,希望能帮助大家更好地完善自己的监控产品。

先来看一下这次分享的主要内容,首先我会介绍一下我们去年开发的前端监控系统 —— 雨燕前端监控,让大家看一下目前蚂蚁最广泛使用的前端监控系统有哪些功能,产品长什么样。

然后是本次的主要议题 —— 如何快速定位问题,在这一部分,我将向大家着重介绍一些我们产品中比较好的产品设计和优秀实践

最后会分享几个其他方面的实践,看一下前端监控系统还可以怎么玩儿。

那么废话不多说,先来介绍一下我们的监控平台。

产品介绍

业务背景

首先先来说一下为什么我们要做这样一个监控平台,主要有以下四个方面:

  1. 蚂蚁内部缺少成体系的前端监控平台

业务方想要做前端监控,可能需要自己从零开始建设,或者是使用一些面向服务端的监控平台来做监控。

  1. 业务增长快,接入成本高

无论是自建系统还是使用其他系统代替,都免不了要有较为复杂的手动接入流程,而面对快速增长的前端业务,亟需一个能零成本接入,默认提供监控功能的产品出现。

  1. 丰富的自定义监控需求

前端相较于服务端,会有更多的业务属性,也会有非常多的自定义监控需求,而现有的产品都很难满足这种灵活的自定义监控。

  1. 不能很好地整合内部其他平台的资源

蚂蚁内部有着非常多的前端基建和数据资产,可以为前端监控带来很多的可能性,但现有的产品没有利用起这些内部资源

雨燕前端监控

由于有上面这些业务场景,我们去年便开始搭建属于蚂蚁的前端监控平台,即雨燕前端监控。

特点

雨燕前端监控主要有以下几个特点:

  • 专注于前端: 结合前端的特点进行产品设计,帮助开发者进行监控
  • 打通研发平台,零成本接入: 让前端应用默认就拥有监控能力,很多业务都是在不知不觉间发现有了监控的数据。
  • 异常监控 & 自定义监控: 除了默认的异常监控外,我们还提供了自定义监控的能力,方便业务方根据自己的业务场景设计监控,甚至有些团队在此基础之上,面向一些通用场景搭建上层的业务中台。
  • 数据资产输出,赋能业务: 监控的数据可以直接通过我们的数据服务进行输出,业务方可以直接消费这份数据,根据自己的需要搭建报表或可视化产品。
  • 整合内部的资源: 包括研发、用户行为的数据,利用这些资源帮助用户发现问题。

架构图

可以看一下我们的架构图,中间主体的部分就是我们的监控系统,下方的研发平台使得我们能够将监控脚本注入到应用中,提供默认的监控能力,右侧则通过接口和数据服务助力业务中台。

  • 整个监控系统,最下方是采集脚本,提供了异常捕获、性能采集、自定义上报以及环境信息采集等能力。

  • 再往上一层是数据任务,包括离线、实时和明细数据。

  • 中间的 DataService 既为平台提供查询异构数据源的能力,又可以面向其它业务进行数据的输出。

  • 监控的服务端主要包含以下功能:元信息的管理,主要是监控项和报警项的配置,然后是能力输出,包括元信息管理接口、报警能力的输出,以及消息推送。还有就是报警消息的处理,用来产生系统报警,比如新增异常的报警,以及智能报警和规则报警。

  • 最上层就是对外展示监控平台前端部分,包含红色部分包含的这些功能。

监控架构图.png

产品功能效果图

这里简单展示一下雨燕监控平台的实际效果,方便给大家有个直观地印象。

首先是监控大盘,能够给用户一个全局的信息展示。大盘上每一个方块代表一个监控项,每个监控项上可以非常直观地展示当日累计的上报数和影响用户数,以及上报数的一个趋势。

每一个监控项点开后,就会进入到监控项的详情之中,能够看到监控项的趋势数据,趋势中比较浅颜色的其实是前一天的对比数据,方便用户拿昨日数据做一个参照。

下方是具体的上报信息,上报信息是监控项之下的一个维度,例如 JS 异常在这里的各种报错信息,每个报错消息也能看到具体的上报数影响用户数。异常信息也可以添加到忽略和关注列表,忽略的异常就不会再计入累计数据中,方便用户过滤掉一些无关紧要的异常。

此外还有一些比较便捷的功能,比如框选趋势图中的某一部分来筛选数据,以及直接对某个 message 查询其明细等等。
功能展示.gif

每个上报信息点击 “分析” 按钮后会进入到异常信息的详情页中,这里可以看到 message 的趋势,以及堆栈映射多维分析等工具。

除了上面的监控数据外,还有报警项的查看

正如上面架构图里展示的,我们有系统报警智能报警规则报警

  1. 系统报警

系统报警是系统默认提供的报警项,最常使用的是新增异常,即对新出现的异常进行报警

通常前端会有一些持续发生的异常,通常这些异常无关紧要,所以没有去修而一直在线上报错。但当出现了新的异常可就需要提高警惕了用户可以收到新增异常的报警去确认异常的原因和严重性

  1. 智能报警

智能报警是有一套智能算法通过对趋势数据进行分析,能够自动探测异常突然升高的情况

  1. 规则报警

规则报警是用户自己设置规则的报警,用户可以选取一个对象,比如某个监控项、或者某个页面。然后可以对选中的对象按一定条件进行筛选。

比如只筛选 message 等于某个特定值的异常。之后就是配具体的触发条件,可以看到我们的产品对于条件的灵活性是非常高的,比如你可以取最近 n 分钟和上一个 n 分钟相比,也可以与昨日同期相比,可以配置增长率下跌率等等。

每个报警项点开后可以看到具体的报警记录,还可以看到产生报警的时候具体的报错数,以及发生异常前的异常趋势

监控流程

通过上面的产品介绍,大家应该对我们的监控产品有了一个初步的认识,通过监控平台的功能可以看到一个完整的监控流程大概包含以下几个步骤:采集感知定位问题处理

其中问题的定位往往是最难的一个环节,这个环节也是最能体现不同产品间差异的地方。那么前端问题的定位到底难在什么地方?

问题定位

难点

第一:信息的缺失

首先是信息的缺失,相较于服务端,如果一个服务端应用出了异常,最不济的方法就是登机器爬一下日志,各种报错信息、堆栈都会打印出来,而且成熟的服务端框架一般也会将一些上下文信息也打印出来。

但对于前端来说,异常的日志是打在客户端的,你是没办法直接去查看的,只能依赖上报,但上报也不可能获取到所有信息,很难了解异常发生的上下文。此外,前端代码会进行混淆,上报上来的堆栈也是无法直接阅读的。

当你获取不到有效信息时,定位压根就是无稽之谈。

第二:可能引起异常的因素过多

  1. 环境因素

引起前端异常的因素也会有很多,首先就是环境因素。前端应用运行的环境可比服务端复杂太多了,机型浏览器操作系统这些都有可能会导致前端出现异常。

  1. 业务因素

前端具有更多的业务属性,代码里的业务判断也会比较多,因为业务导致的异常也比较常见,比如不是会员的用户却看到了会员才能执行的操作,导致了请求或 JS 的异常等等。

  1. 交互因素

此外,前端会有更多的交互,交互复杂的代码里很容易有一些边界条件测试时没有测到,最终导致线上的问题。

因素一多,问题的排查就经常会毫无头绪,如果是对着上报日志逐一去看,除非有过人的探案能力,否则很难在第一时间就找到问题的原因。

对于问题定位的要求

排查的难度上去了,但与之相应的留给排查的时间却并没有增多,相反由于前端直接被用户感知,对于问题的排查速度反倒有了更高的要求。因此对于问题定位的要求就两个字,“” 和 “”,即排查问题的效率准确度

优秀实践

接下来,就让我来给大家分享一下我们在问题定位这方面有哪些优秀的设计和实践。

自动堆栈映射

对于异常来说,很多情况下都是因为代码本身的问题导致的,此时要定位问题的话,最好的方式就是去看堆栈

之前说过,对于前端而言,由于前端的代码需要混淆,因此本身堆栈是不可读的,所以当你拿到堆栈后,需要通过 SourceMap 将堆栈的信息映射到源代码才能进行进一步的问题排查。

堆栈映射的技术本身已经可以说是十分完善了,无非就是将堆栈与 SourceMap 结合,最终看一下问题具体出现在源码哪一行。SourceMap 的生成映射都有现成的工具,那么这部分有什么好讲的呢?

刚才说过,问题的排查除了要准,还有就是要快。堆栈映射本身做到了 “准”,那么 “快” 则是通过全流程的自动化来实现的。

手动映射效率低下

这里先来说一下手动映射到底效率有多低下。

  1. 生成结果不稳定

本地生成的 SourceMap 很有可能和线上代码匹配不上,最常见的原因是依赖版本的变化

  1. SourceMap生成慢

首先是 SourceMap 生成本身就很慢,一个应用生成 SourceMap 超过 5 分钟是很正常的事情,如果你是发现问题了再去生成 SourceMap,将错失很多宝贵的时间。

  1. SourceMap 的检索和管理

即使你有了 SourceMap,映射的过程如果手动来操作,也是非常恼人的,想象一下你要对着堆栈的每一行找到具体的 SourceMap 产物,这个过程也不会快到哪里去

  1. 不便于分享

对于有多人协作,甚至是多个团队协作的项目,发现问题的人和修复问题的人很可能是不同的人,如果只是用工具手动进行映射得到的结果是无法直接分享给他人的,只能通过截图传文本等低效的形式来让其他人获取有用的信息,甚至需要他人自己手动再映射一遍。

  1. 无法与其他辅助信息结合

堆栈信息虽然很有用,但也不是万能的,即使获取到源代码也需要数据和辅助信息才能更好地进行定位,尤其是需要特定场景才能复现的问题。手动映射后,再到监控平台去查看信息,宝贵的时间就这样流逝了。

所以,全流程的自动化是快速排查问题的关键所在。

自动化实践

那么是不是只要构建的时候开启 SourceMap,发生异常了将堆栈报上来就可以了呢?其实并没有这么简单。

SourceMap 生成

  1. 开启 SourceMap 的问题

获取到 SourceMap 最简单的方式,莫过于直接在构建时开启 SourceMap,现在主流的构建工具基本只要改个配置就可以实现。但默认开启 SourceMap 有以下两个问题:

  • 构建耗时长,影响构建效率: 一个应用开启了 SourceMap 后,构建时间可能会增加好几倍,如果业务少还好,但如果业务已经成规模的话,这将会严重影响整个公司的构建效率。
  • 容易出现构建异常: SourceMap 非常吃内存,经常会出现 OOM,如果因为构建 SourceMap 失败阻塞了业务发布,是得不偿失的。
  1. SourceMap 异步生成

为了解决这些问题,我们是采用了 SourceMap 异步构建的方式来解决的。

如下图,在我们的构建平台,当一个应用开始构建时,会在其他的机器上起一个异步构建的任务来进行 SourceMap 的生成,相当于构建两次。这样便可以在不阻塞正常构建流程的情况下生成 SourceMap。

同时,因为是在不同的机器上进行构建,也防止了 SourceMap 出错导致影响到正常构建。

不过分为两次构建可能会出现构建产物不一致的情况,这样即使有了 SourceMap 也无法进行映射。通常出现这种问题的原因是依赖版本不一致造成的,对于这种情况,通常需要锁定依赖树去解决,在正常构建流程中先生成 lock 文件,然后 SourceMap 构建任务根据锁定后的版本去获取依赖。

或者干脆使用同一份依赖也是可以解决的。当 SourceMap 生成好以后,可以保存到只有内网能访问的 CDN 上,这样便于监控平台去获取

堆栈上报优化

那么有了 SourceMap 后,还需要有异常的堆栈才能最终定位到问题,最简单粗暴的方式就是将堆栈原封不动地采集上来。

  1. 全量堆栈上报的问题

全量的堆栈上报虽然不影响问题的排查,但会导致其他问题。

首先堆栈具有以下特征:

  • 堆栈文本量大: 通常一个堆栈的字符数在几百到几千之间,远比其他需要上报的数据多得多。

  • 重复上报无意义: 当你同一个异常发生 1000 次,也只要一份堆栈即可,多上报毫无意义。

如果全量上报,会带来以下问题:

  • 对于前端来说,大量的上报会影响前端性能,并浪费流量(影响用户使用)
  • 对于数据端,会浪费数据存储计算的资源(公司成本会上升)。目前监控平台每天有堆栈的异常大概有 2000万条上报,而一个堆栈经过 encode 之后差不多有 2KB左右,那么如果全量上报,每天堆栈的存储量大概就有 40TB 这样一个量级。
  1. 优化方案

要想解决上面的问题,可以采取以下两个方案来优化堆栈的上报

  • 堆栈压缩:减少单次堆栈的容量

  • 防止堆栈重复上报的机制

2.1 堆栈压缩

堆栈压缩的原理就是尽可能将重复的内容提取出来,用简短的标识进行替代,上报时将替换后的内容连同标识映射关系一同上报

对于大部分前端场景,重复率最高的就是文件的 URL。可以看一下下图,URL 被替换成了 # 和数字组成的标识,替换后可以有效地减少堆栈的文本量。(图中的原始堆栈中的 URL 是 chrome 对展示做了优化,实际的 URL 还要长很多)。

2.2 防止重复上报

防止重复上报最重要的一点就是:怎么判断是否是重复堆栈?
我们的方案就是给每一个堆栈计算一个 ID,当 ID 相同时就代表这些堆栈是同一个堆栈。这个堆栈的生成的方法是:

  • 堆栈归一: 首先是对堆栈做归一化处理,抹平不同平台下堆栈的形态差异

  • 指纹提取: 将堆栈按一个稳定的算法提取其中若干行,拼接起来作为指纹,这一步的目的就是为了提升生成 ID 的性能。

  • 生成ID: 将上一步的指纹使用 MD5 算出一个固定位数的 hash 作为 ID。

有了 ID 之后,我们还要探测这个 ID 的堆栈是否上报过,最简单的方式其实是请求服务端接口查询一下,但对于大部分服务端接口都难以承载堆栈探测这种量级的请求。所以我们采用了一个比较机智的方法来完成堆栈上报的探测。

当服务端接收到某个堆栈后会往 CDN 发一个带有堆栈 ID 的空文件。而采集脚本发生异常,获取到 ID 时会用ID 去访问 CDN 资源,如果请求成功,说明堆栈上报过,就不再上报;如果是 404,则说明是全新的堆栈,需要进行上报。CDN 本身设计上就是能够承载大量访问的,因此就解决了探测的问题。

整个防重复上报的流程如下:

平台映射

有了 SourceMap 和堆栈,剩下的就是在前端将其映射即可,npm 上的 source-map 这个工具就可以实现。此外,我们还更进一步,因为和研发平台打通,所以能够获取到代码,顺便将定位信息以源码的方式展示了出来。

最终的效果就是,你一打开页面就能看到异常发生的位置上下若干行的代码,非常直观地了解到哪里出了问题。

堆栈映射.gif

堆栈不是万能的

虽然自动堆栈映射非常高效,但也有其解决不了的问题:

  • 有些异常,尤其是自定义的业务异常,本身就没有堆栈信息

  • 即使有了堆栈,也无法重现

遇到这种情况就需要借助数据的力量了。

数据分析

通过数据来进行问题的排查也是比较常用的手段之一。

正如之前所说,前端导致异常的因素有很多,包括环境因素、业务因素以及复杂的交互等,数据分析的用处就是帮你在诸多因素中缩小排查的范围

因此数据分析功能的设计一定要能达到缩小范围的目的,切忌无用数据的堆叠!

多维分析

前面总结了很多可能导致异常的因素,当一个异常发生时,常常会让人无从下手,单单通过查看日志的方式无法推断出问题的原因。如果能将问题的范围集中在某一两个维度上的话,就能够大大降低问题排查的难度,更容易将问题复现。

下面看一下这张图,这就是我们监控平台 “多维分析” 功能的界面,最左侧是一些比较大的维度,其中包括了前端通用的维度,比如页面、浏览器、操作系统等等,还有一些业务自定义的一些维度。

左侧对于一些分布比较集中的维度会打上标签,使用者可以重点关注。

每一个主维度点进去可以看具体的分布情况,有一些维度还有子维度,可以进一步分析,比如发现某一个异常非常集中在某个品牌的手机之下,而进一步这个品牌的数据又非常集中在某一个版本的支付宝,可以将问题排查的范围进一步缩小。

那会不会有在每个维度都不集中的异常呢?当然是有的,但这种问题要么是发生的概率非常非常小,要么就是一些无关痛痒的问题,否则普适性又严重的异常通常在测试阶段就很容易发现,根本不会出现在线上。因此这种多维度分析的功能往往比预想的还要管用

自定义维度:为了应对因为业务因素导致的异常,我们的监控体系增加了自定义维度的支持,用户可以自己根据自身的业务需要设置一些维度,上报的时候将具体的业务值上报上来,和其它通用的维度做相同的聚合展示,这样就能够增强对业务因素导致异常的定位能力

如果用我之前举得例子,那么可以把是否是会员作为一个维度,这样像类似 “非会员用户执行了会员才能进行的操作” 这类问题,就可以比较容易发现了。具体的业务维度如何设置,需要结合各自的业务模型去看,这里也不多赘述了。

关注操作

之前说过前端还有一个特点,那就是重交互,一个页面可能会具有非常复杂的交互,而有些问题可能就是需要在某些特定的交互下才能触发。对于这种问题,最需要的就是重现问题的步骤

许多监控工具都有很多的尝试,比如录屏。这种黑科技确实能有助于问题的排查,但技术难点比较多,性价比并不是很高。

最简单的方式可以记录一下用户出现异常前的几个操作。这样最后会得到一个用户的一个操作步骤,根据用户的操作步骤,便可以试着去重现,也就比较容易定位到问题。

如果公司内已经有了埋点数据,那么直接将埋点数据与异常数据关联是最经济的方式。

关注变更

什么时候最容易产生新的异常?那就是变更的时候,变更包括代码的发版,或者所依赖的环境发生变化,比如你的页面是嵌在自己的 APP 中,即使你自己的页面没有变化,APP 发版也一样可能导致新异常的出现。

因此,如果能将异常的出现与具体的变更时间点关联起来,则能够将排查目标缩小至这次变更上。

变更是一个时间点,因此将这类时间点标注在趋势图上能够最直观地将异常和变更关联起来。当你看到异常趋势明显升高,而时间点正好与你的一次发布重合,那基本可以确定是这次发布导致的了

这里截的图比较有意思,这其实是个反例,当你修复了某个异常并发布上线后,也可以很直观的看到异常数的下降

具体标注哪些信息可以根据自身的业务需要进行调整,比如之前所说的页面发版、APP 发版。可以将一些其他变更信息也带上。

比如你页面的这次发版,依赖发生了哪些变化,是不是依赖升级导致了异常的发生,这些都可以协助你进行问题的排查。

其他优秀实践

自动脚本接入

雨燕前端监控的卖点之一,就是默认提供异常监控的能力。要做到这一点,最重要的就是自动将监控脚本接入到业务应用中。

这件事情其实并不复杂,但却极大地减少了业务接入监控的成本。整个接入流程主要就是研发平台将监控的项目 ID 注入到构建容器中,然后构建工具去获取参数,并将脚本和参数注入到前端构建产物中。

要做到这件事情离不开完善的研发基建和统一的技术栈,对于业务还在增长阶段的企业,可以有计划地在这两个方向发力,为未来承载大量业务做准备。

自定义监控体系

之前已多次提到过,前端有着非常丰富的自定义监控诉求,为此我们提供了一套非常完善的自定义监控体系,默认的异常监控其实也是依托于这套体系之上。

通过上面的图可以看到,我们的监控平台从监控项、以及监控项下的指标、维度、字段这几个层面提供了自定义的能力:

  • 自定义监控项:这是一个比较大的维度,在我们监控平台推出性能监控之前,有的业务会通过这个功能去做性能监控,所以会有诸如 “页面加载时间” 等监控项

  • 自定义指标:这类自定义的值,会在趋势数据里进行展示,如果还用 “页面加载时间” 举例,可以将最大值、最小值、平均值设置成三个指标,这样能够从多个角度分析性能。

  • 自定义维度:之前在多维分析使用的 “是否是会员” 就是一个自定义维度的例子,这里就不再赘述了。

  • 自定义字段:这是一种不会用来做任何加工的字段,仅在明细数据时会展示出来,业务方可以将一些纯粹的辅助信息设置在自定义字段中。

直接这样讲可能比较抽象,可以结合具体的设置界面来理解:


其它可能性

本次的分享在这里就到了尾声,但前端监控的探索之旅才刚刚开始,还有许多问题值得大家一同去探索:

  • 自动判断问题的严重程度: 前端有很多异常是不需要去处理的,但也有很多错误是非常致命的,如何自动判断问题的严重程度将能帮助我们在第一时间进行决策。
  • 更智能的报警,减少误报率: 现在大部分监控产品都能做到不漏报,但在不误报上都做的不太好,真正有威胁性的报警可能会迷失在大量无需关注的报警,最终变成 “狼来了” 的故事。
  • 异常自愈: 发生了异常,是否能根据异常自动生成修复方案,减少问题修复的成本。

如何基于数据和堆栈映射快速定位问题.mp4 (121.63MB)

如何基于数据和堆栈映射快速定位问题.mp4 (121.63MB)