找回密码
 会员注册
查看: 12|回复: 0

得物客户端自研崩溃日志平台探索

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
71916
发表于 2024-10-7 18:39:39 | 显示全部楼层 |阅读模式
得物客户端自研崩溃日志平台探索背景长久以来,得物采用Bugly来做崩溃日志收集,用于治理得物App崩溃问题,Bugly作为行业内的标杆产品,为App行业的发展做出来巨大的贡献,而随着得物App规模的进一步扩大,多条业务线并行开发,得物App版本迭代的效率要求进一步提高,在千万日活的背景下,快速发现并解决App产生的Bug,有着重要价值,因此得物自研Crash平台,以进一步提高Crash治理效率。名词解释:Crash:App发生了崩溃,导致应用退出,本文称之为Crash,Crash主要分为Java Crash、Native Crash以及ANR。Bug:不同设备上的App发生Crash可能是同一处代码问题导致,这批Crash合并成一个问题后,这个问题本文称之为Bug。?得物Crash平台解决的问题?在与Crash保持一致的使用习惯的前提下,得物Crash平台主要解决了两类问题:自动指派Bug给研发人员,得物Crash平台能够自动分析Bug的堆栈以及页面信息,并根据git中的代码-研发的管理,自动指派Bug给相关的研发人员;自动分配Bug给业务线,得物Crash平台能够将Bug自动分配给业务线,按业务线治理Bug成为可能,同一App下的不同业务线同学只需要关注其所在业务线即可,解除了业务线间的耦合。接下来,先上图。?主页展示崩溃率、崩溃排行榜以及TOP20的Bug,尊重行业习惯,与Bugly不同的是,得物Crash平台的指标走势能够按业务线划分。Crash列表页支持查询不同业务线的问题,并且根据App的用户ID查询该用户产生的所有Bug。Crash详情页也是保留了传统的上报趋势、设备分布以及堆栈,Bug被自动指派后,Crash平台会对接飞书给研发人员发送堆栈。?以上便是Crash平台的主要功能。?Crash平台结构设计本文站在巨人的肩上,沿用Bugly的习惯,采用xCrash作为采集SDK,将采集的Crash存入阿里云SLS中,再通过Flink进行消费,流入到Crash平台。Crash平台涉及到Crash捕获、堆栈聚合、指派以及指标计算。其结构如图所示:?Crash平台工作流程如下:平台依赖App上的Crash采集SDK,捕获App产生的Bug并上报到阿里云SLS,采集的Bug类型包括Java Bug堆栈、Native Bug堆栈以及ANR时的堆栈;本平台采用Flink以及定时任务从阿里云SLS中获取已上报的Bug并流入到Crash处理服务中,Crash处理服务将Bug进行初步去重后入库到基于ClickHouse集群的原始日志集群中;已去重的Bug将流入到堆栈聚合流程中,该流程将来自各项设备的Bug进行堆栈聚合匹配,并统计出按照用户/设备/版本/日期维度区分的每个堆栈的Bug次数以及影响设备数;已聚合并且未指派的堆栈流入Bug指派流程中,这一流程针对堆栈的特征与代码-组件-人员记录进行匹配计算,从而得出堆栈的归属研发,并将该堆栈通过飞书/钉钉等工具推送给研发人员;本平台提供查看Bug列表以及趋势曲线的Web端,Web端支持基于版本/时间/类型/业务线/研发人员查看Bug的走势以及Bug列表,从而全方位Bug治理的指标。Crash平台关键步骤本平台从阿里云SLS获取Crash数据后,主要包括Crash去重,Crash合并、堆栈聚合、Bug指派这么几项功能,当Bug被指派成功时,平台调用飞书通讯工具API将堆栈推送给研发人员。?某些Crash发生时,用户可能会重试,出现连续崩溃,平台针对最小单位时间段(一个小时)内的Crash进行去重,去重步骤如下:先针对Crash堆栈内容进行MD5运算,得到该堆栈的唯一字符串;以步骤二的唯一字符串为Key,将最小单位时间段内得到的Crash进行聚合,得到HashMap;针对步骤三的HashMap,取出无特定用户信息的Crash堆栈结构体,增量入库到Crash堆栈记录表中;针对步骤三的HashMap,取出用户信息,进行用户信息合并,合并时将发生条目累加,从而得到某个Crash堆栈在单一设备上的单位小时内发生次数;将用户信息增量入库到Crash用户信息表中;将步骤三的Crash堆栈结构体与步骤四的用户信息组装成Crash对象,从而完成Crash去重。上述步骤有效降低了数据冗余,实际情况下,App也不会发生多个种类的Crash大规模爆发,通常是某种Crash出现上升,因此,上述步骤能够减小后续堆栈聚合的压力,并让堆栈聚合保持稳定。Crash去重后得到的一批Crash对象中,也可能合并成同一个堆栈,例如当某个组件发生崩溃时,App多处调用该组件的代码都会产生崩溃,不同的调用处所产生的Crash堆栈有区别,但是这其实是同一个问题,需要被合并,而这也是本平台堆栈合并的用途。因此,Crash去重后进入Crash合并阶段,这个阶段会将每一项Crash堆栈进行特征提取,采用提取的特征进行特征计算,批评成功后即合并,从而完成Crash合并,实际情况下,App通常是某种类型的Crash出现上升,当某种Crash出现时,也不会只有一次,因此这一步骤能够进一步减小后续堆栈聚合的数据规模。Crash合并合并后进入堆栈聚合的流程,该流程会先将当前平台中的该类型的Crash全部取出后,以与Crash合并步骤相同的规则组装出堆栈特征,然后用Crash合并合并得到的堆栈特征与平台的堆栈特征进行匹配计算,匹配成功后即合并,全部遍历后,未被合并的Crash则是新的Crash,申请新的唯一ID并入库,从而完成堆栈聚合。所有参与堆栈聚合的Bug ID均传入到Bug指派流程,该流程以新的特征提取规则提取Crash堆栈中的代码特征,与本平台所缓存的代码-组件-人员关系进行匹配运算,一个Crash堆栈可能匹配出0到N个研发,将研发匹配出的权重以及行数进行排序,最高的研发则是该Crash堆栈的负责人。对于Java崩溃以及如果堆栈没有匹配到负责人,则通过该堆栈发生的页面类名来产生负责人。对于指派成功的堆栈,直接通过飞书和钉钉等企业通信工具推送给研发人员,对于未指派成功的消息,则推送给架构组。Crash去重、堆栈聚合以及Bug指派这三个步骤中均会产生统计信息落库到平台数据库中,产生支持版本/时间/类型/业务线/研发人员这些维度查询的数据。Crash堆栈聚合虽然本文站在巨人的肩上,但是巨人并没有开源堆栈聚合这一算法,该走的路还是得自己走。本文设计了得物Crash聚合算法,在本平台中堆栈聚合是重要的功能,堆栈聚合的流程如下图所示。本平台从阿里云SLS存储中获取Crash堆栈,流入到Crash处理服务集群中,经Crash去重后即开始Crash合并以及堆栈聚合,Crash合并流程将去重后的Crash进行特征提取,随后本平台会将数据库现存的这个类型的堆栈的特征全部取出,并进行堆栈特征组装,进而开始堆栈聚合,聚合后开始判定,如果匹配度达到90%(数值可配置),则认为堆栈可聚合,如不能聚合则属于新增的堆栈,申请新的ID服务,聚合完成后,所有参与聚合的堆栈ID,都流入到堆栈统计中。特征提取算法是堆栈聚合的重要部分,本平台特征提取算法与匹配流程的协作如下图所示。?特征提取算法从上图示例堆栈中得到Crash类型、Crash消息内容、App业务代码、第三方依赖库代码、Android框架代码以及Java框架代码,当两个Bug的堆栈进行合并时,会分别匹配Crash类型、Crash消息、以及代码,最终得出匹配计算的结果,当结果高于系统配置的数值时,则认为这两个Bug的堆栈可合并,这两个Bug属于同一个Bug。?特征提取与匹配步骤一,将堆栈按行拆分成数组,转入步骤二;步骤二,读取数组第一项,读取后判断是否包含:,包含则切分,:左边为Crash类型,右边为Crash消息内容,当不包含:时,则第一项作为Crash类型,转入步骤三;步骤三,读取数组下一行,没有下一行则结束,对文本进行包含判断,当该行包含App业务代码特征时,则取出App业务代码的权重;当该行不包含App业务代码,包含第三方依赖库代码时,则取出第三方依赖代码权重;同理取出Android框架代码以及Java框架代码的权重,转入步骤四;步骤四,去掉改行的函数括号部分,将(后的内容去掉,剔除行号对聚合的影响,解析$符号,判断得出内部类特征,当下一行存在时,转入步骤三,否则算法结束。上述流程中,App业务代码、第三方依赖库代码、Android框架代码以及Java框架代码均采用人工配置进行管理,例如本公司业务代码的报名包含duapp,第三方依赖库包含xxx.xxx,Android框架的代码普遍包含android或者androidx,Java框架的代码多以Java开头,这些基本上不变。在权重配置方面,App业务代码>第三方依赖库>Android框架>Java框架。特征提取算法得到包含权重的堆栈特征,然后开始匹配计算,匹配计算的算法流程如下:步骤一,判断Crash类型,如果类型不一致,则匹配失败算法结束,如果类型一致,进入步骤二;步骤二,逐行判断代码,当一致时,标记find为1,当不一致时,标记find为0,当前堆栈得行数遍历完成后,进入步骤三;步骤三,累加每一行的权重,得到分母,累加每一行的权重*find得到的值,得到分子,进入步骤四;步骤四,当分子除以分母得到的值大于配置的数值时,聚合判定成功,可合并,否则失败,算法结束。匹配算法结束后,平台针对所有未合并的Stack会批量申请唯一ID,这些得到了唯一ID的未合并的Stack则是平台新增的Stack;上述唯一ID加上平台Stack记录中得到的堆栈ID,则是本次参与过堆栈合并的ID,将全部流入到Bug统计流程,重新统计基于时间段/设备的Crash发生次数,而App的设备信息关联的到App版本、系统品牌、系统版本信息,便可支持基于版本/时间/类型/业务线的Bug查询服务。Bug指派流程巨人并未考虑Bug自动指派,本文自行实现。Bug治理过程中采集到的堆栈最终需要指派给归属的研发来进行修改,手动指派人员+发送邮件通知的方式,不易于维护且实时性不高,本平台设计了自动指派流程,减少手动指派率,能够有效提高Bug的治理效率,指派流程如下图所示。?指派流程从平台的Bug记录中取出一批Bug,进而对这批Bug进行特征提取,得到面向代码的堆栈特征,然后转入指派计算,与代码-组件-研发关系记录计算结束后,进入指派判定流程,当判定成功时,则通知代码作者,判定失败时,则通知架构组处理。堆栈特征以及代码-组件-研发是Bug指派的重要内容,其协作流程如下图所示。?堆栈特征的本质是代码片段,代码片段包括自定义消息、方法、行数以及类名,其中,类名包含内部类以及kotlin转换类。App的Crash堆栈包含Java Stack、Native Stack以及ANR和自定义异常这些其他异常,这三类Crash中,Java Crash是有堆栈的,Native可能没有堆栈,ANR则是一段时间的运行结果,也不一定能得到正确堆栈,因此,有堆栈的则进行堆栈解析,没有堆栈的则得到Crash发生的页面路径,进而根据页面路径得到类名,在逐行解析的过程中,进行权重配置,从而得到包含权重和行数的指派特征。代码-组件-研发关系则是从代码的Git仓库以及研发配置中得到的代码与人以及代码与组件的关系,其中,从Git仓库分析得出的代码-组件-研发关系占主流,算法步骤如下:步骤一,建立组件负责人关系表,维护组件-负责人关系;在aar打包前阶段采集组件信息,git rev-parse HEAD 获取当前commit id,然后执行git rev-parse HEAD获取当前commit id,遍历src/main/java/及src/main/kotlin/通过git ls-tree -r --name-only HEAD获取到文件最后修改信息,从而得到文件名-研发关系,并将这些信息写入关系文件(本平台命名为developer.txt)并且打入aar中,转入步骤二;步骤二,在App打包阶段采集第三方依赖库的组件信息,在App打包的prebuild阶段拿到App运行时依赖的第三方库,解压收集类名并与构建单中的组件代码-组件关系,转入步骤三;步骤三,将步骤二得到的组件关联上步骤一的负责人,从而得到代码-组件-负责人的关系;在App打包阶段,通过gradle dependence 获取App组件构建清单,在prebuild阶段去遍历依赖aar解压后的文件,并用构建清单过滤不必要文件,文件中存在developer.txt就追加到merge-developer.txt. 如果不存在就去解压jar包获取类信息添加到merge-developer.txt,作为apk打包产物;转入步骤四;步骤四,Bug指派阶段根据版本号获取该版本的developer.txt文件,并解析得到代码-组件-研发关系,其中研发包含某个类的作者以及当前的组件的负责人,将代码-组件-研发关系存入redis缓存后,获取流程结束。匹配计算时,先将堆栈解析得到的指派特征与代码-组件-研发关系进行逐行匹配,从而为每一行指派特征找到研发人员,当研发人员离职时,则找到组件负责人。得到负责人后安装指派特征的权重进行逆向排序,当权重一致时,则根据指派特征再堆栈中的行数进行正向排序,第一个则是当前Bug的归属人。当Bug无法找到的时候,根据页面路径来指派研发,而页面的本质也是某一个类,因此可以找到一系列的路径上的研发,最近十个路径节点中比例最高的研发人员作为堆栈的归属人。归属人找到后,立即给该研发的飞书发送消息,从而实现了自动指派Bug。?业务线计算流程?Crash平台旨在提升App生命周期中的Bug治理效率,Bug治理需要从多个维度,实时关注崩溃次数以及影响设备数指标,指标最终落地到业务线上,因此需要计算出基于业务线的Bug治理指标。本平台从阿里云SLS中获取数据时,便会以最小时间点作为窗口,直接查询一个小时内(例如从当天1:00到2:00)的Crash记录,这些记录便具备了时间点属性,时间点包含日期和小时,然后将日期和小时提取出来作为单独的属性,在生成统计信息时,会分别对日期和小时这两个维度生成统计记录。在查询的时候,如果查询当天时间段的数据,则直接查询时间段内小时维度的数据,查询跨天时间段时,则查询时间段内日期维度的数据,这种独立的统计信息能够提高查询速度。本平台采用Flink获取SLS数据,针对Flink设置基于时间点的窗口,也会采用定时任务作为Flink不可用的兜底策略,在定时任务中五分钟取一次Crash数据,将Crash数据的发生日期转化为时间点。针对业务线属性,在指派到人员时,根据代码-组件-研发关系(研发关系包含代码作者以及组件的负责人),传统做法直接根据Bug归属人定位到其所在的业务线存在局限性,对于大型App而言,往往有数条业务线同时开发,某些需求需要多条业务线的同学协作完成,就存在不同业务线的同学相互修改代码的情况,因此直接通过人定位业务线的方式并不准确,不同业务线之间相互调用组件也让业务线的定位相对复杂,本平台设计了为Bug定位业务线的算法,步骤如下:步骤一,针对堆栈进行解析,得到堆栈的代码特征,其特征包含代码段以及代码段的权重,进入步骤二;步骤二,将步骤一得到的堆栈的代码特征与代码-组件-研发关系进行匹配计算,得到一组研发人员,根据权重和行数进行排序后,得到Bug归属的研发人员,进入步骤三;步骤三,在步骤二中得到Bug归属的组件,并取出组件负责人,进入步骤四;步骤四,取出组件负责人所在业务线,是则定位到该业务线。业务线查询流程如下:?总结本平台上线两个月(2021年5月1号-2021年7月20)来,共获取Java Bug数千个,自动指派超过40%;共获取Native堆栈上万个(缺少符号表,因此没有特意指派),其工作模式受业务方认可,其中数百个Bug已经被处理完成,还有数百个Bug在处理中与数百个Bug待处理。?未来需要重点研究的方向有三项:堆栈聚合算法,本平台Java Crash是满足预期的,Native Crash由于缺少符号表解析,聚合效果不如Java Crash,需要优化Native Crash。优化指派算法,由于当前几乎依靠堆栈进行指派,尚未开启按照行为轨迹进行指派,所有的OOM以及大多数Native堆栈都无法自动指派,因此,本文后续的重点研究方向是提高自动指派率。多维度报警以及根据堆栈进行查询,Crash平台数据库采用ClickHouse与Mysql结合的方式,在更改状态以及聚合查询有着很好的优势,多维度报警只是业务需求,而在现有的结构下,长文本检索就难以支持。本平台后续将会借助Elasticsearch支持堆栈搜索。?招聘相关? ? ? 得物App无线平台Android架构,负责得物App Android客户端基础模块的研发和维护,我们致力于性能,稳定性,客户端用户体验,沉淀流程规范和配套工具、提升研发效率。? ? ? 团队技术一流、氛围良好,感兴趣的同学简历可以发送至? ? ??tech_culture@shizhuang-inc.com关注得物技术,携手走向技术的云端文|hc
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-10 19:08 , Processed in 0.463582 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表