指南:随着业务的快速迭代发展,系统对业务的监控和优化不再局限于行为和性能监控。前端异常监控更能体现用户的真实体验。细粒度的监控可以主动发现问题,及时减少损失,针对性的分析和治理甚至可以带来业务收益。本文结合广告托管团队异常监控治理经验,介绍异常管理采集、告警监控、排查分析、治理优化的实用总结。全文8455字,预计阅读时间19分钟。1.前言行为、性能、异常管理在前端领域是老生常谈。在实践中,很多团队也采用了这样的管理方式:行为>绩效>异常。不难理解,从团队收益的角度,行为统计在短期内更直观,有些管理本身就是业务需求,比如功能上线后的PV、UV统计。一般来说,对于在线服务来说,后端的异常监控是必须的,服务异常的主动发现大多来自于后端。前端异常监控能起到什么作用?从管理者的角度来看,加入这样的投资是否划算?如何监测异常,更快发现并引导止损?面对这些问题,很多业务的前端异常监控工作还没有开始就结束了。我们团队在实践中总结了一些思考和经验,希望对读者有所帮助。1.1业务背景介绍我们是一家百度广告托管公司,承接多个行业的网站建设工作。这包括移动/桌面网站、小程序、HN(百度App的ReactNative-like解决方案)等各种载体,每天都有大量的流量。对于网民来说,我们需要保证流畅的阅读和互动体验。对于广告商,我们需要提供高质量的保障。通过前端异常监控和治理,业务团队获得了及早发现问题、及时止损、优化广告效果等诸多收益。1.1.1需要解决什么问题?正如文章开头介绍的,在业务发展的相当一段时间里,团队的重心一直放在后端监控和告警的提升上。但是当我们把服务的稳定性管理到一定标准的时候,我们发现有些线上问题还是很难回想起来,比如整个页面或者部分页面渲染异常,影响体验,甚至影响广告的转化和成本.可能原因示例:静态资源加载异常,包括脚本资源、图片素材等API访问异常JS执行异常与后端异常监控相比,资源加载和JS执行异常是前端异常监控带来的增量场景。端到端的接口稳定性更贴近用户的真实感知,更能体现网络对稳定性的影响。小流量场景问题早发现产品发布往往需要小流量和AB测试验证。或者某些问题仅在某些特定场景中触发。由于流量限制,很难通过服务数据的波动发现。由于扩张造成较大的负面影响或发现客户投诉。前端异常监控可以帮助我们解决这些场景下的问题。下面将从异常收集、完善监控告警、异常排查、异常治理等阶段介绍我们的主要工作和经验。说明:本文更多的是从业务应用的角度来讨论问题,对于通用的嵌入式服务、数据处理、展示平台没有过多的讨论。幸运的是,团队已经有了这样的专业人才和平台。我们结合自己的业务场景需求,与平台共同设计并支持了通用监控之外的业务异常监控,后面会介绍。===2.异常采集第一步是将异常情况以点的形式发送给采集服务。这包括通用的解决方案,比如很多文章中提到的通过窗口监控捕获的错误,以及一些更细微的对业务有很大影响的业务异常。2.1通用异常收集通用异常收集是一种非侵入式的异常收集方式,不需要业务开发人员主动表达。当系统出现异常时,通过事件冒泡、事件捕获或者一些框架提供的钩子函数来收集错误。在页面中收集异常时,主要涉及两类场景:网络请求导致的资源加载异常,如图片加载失败,运行时异常导致的脚本链接加载失败,大部分是由于某些代码错误导致的资源加载兼容性或未考虑边界条件导致的异常,业务中有两种监控方式:使用资源自身的onerror事件,资源加载失败时报错。这种场景一般需要借助打包工具在打包代码的时候给相关资源添加onerror逻辑,比如使用script-ext-html-webpack-plugin给所有的script标签添加onerror属性。使用:window.addEventListener('error',fn,true)针对运行时产生的异常,通常我们使用如下方法进行监听:在页面顶部添加如下事件:window.onerrororwindow.addEventListener('error',fn)但这种处理方式也有其局限性。无法捕获未捕获的承诺产生的异常。因此,在业务使用中,一般会额外增加一个事件监听的方法来捕获未处理的promise异常。window.addEventListener('unhandledrejection',fn)对于运行时产生的异常,一些前端框架也为我们提供了配置方法来简化我们的日常开发。React框架:React16之后,框架支持componentDidCatch来捕获渲染过程中的异常。但是使用的时候需要注意,看错误边界(https://reactjs.org/docs/erro...)服务器端renderingErrors在错误边界本身(而不是它的子项)中抛出Vue框架:Vue框架还提供了一个类似全局的错误配置。以下方法可以为组件呈现和查看期间未捕获的错误指定处理程序。Vue.config.errorHandler=(err,vm,info)=>{}从2.2.0开始,这个钩子也会捕获组件生命周期钩子中的错误。此外,当此钩子未定义时,捕获的错误将通过console.error输出,以避免应用程序崩溃。从2.4.0开始,这个钩子还将捕获Vue自定义事件处理程序中的错误。从2.6.0开始,这个钩子还捕获v-onDOM监听器中抛出的错误。此外,如果任何重写的钩子或处理程序返回一个Promise链(例如异步函数),来自其Promise链的错误也将被处理。注意:在捕获的异常中,经常会看到错误信息为“Scripterror”的异常。出现此类异常的场景是网站请求并执行跨域脚本。如果脚本报错,会在全局异常监控方法中捕获错误信息为“Scripterror”的异常。由于浏览器的安全限制,这里没有显示具体的错误信息,对排查问题很不友好。当前项目打包后的大部分资源文件都会单独部署在CDN服务上。资源引用域名为加速域名,与运行页面域名不一致。常见的解决方法是使用打包工具,在脚本链接中添加:crossorigin(https://developer.mozilla.org...)属性,在返回头中添加access-control-allow-origin:yourorigin资源.com。这样,当通过CDN地址导入的JS在运行中报错时,全局错误监控方法可以获得完整的错误信息。2.2业务异常采集2.2.1如何定义业务异常在通用异常采集的基础上,系统增加了业务自定义异常的管理。是一种“埋点”开发方式(相对于上层开发人员没有感知到的“无埋点”方式)。开发者在程序中显式发出数据点,往往伴随着当时的一些运行数据。为什么要这样增加?标准方法收集的异常数据量有限,大部分是异常堆栈。但是还是有一些场景不能很好的满足:虽然不能直接从底层抓取,虽然在控制台看不到红色的报错,但是从业务角度还是需要注意一些问题。例如:在APP下载业务中,客户需要将渠道下载包绑定到页面,然后使用页面进行广告投放。有时候客户弄错了,把安卓下载包放到了iOS上。这种情况在页面渲染阶段不会造成任何异常,但显然不利于广告的转化。从业务的角度来看,需要发现和解决。某些异常堆栈信息不足。我们需要获取一些运行时数据,以辅助后续问题的定位和分析。比如当前访问的账号id,当时应用状态的一些关键业务数据等。比如某业务发现大量“onAndroidBackisnotdefined”异常,携带的产品线id异常信息用于快速定位导致异常的产品线。与开发同学沟通后,定位到问题代码,进行业务。兼容的。分析成本高,告警时效性低如果实践过,你会发现异常数据的分析统计工作并不容易,尤其是在非常笼统的异常栈中发现更精准的问题,及时告警、排查、止损。业务异常可以让我们在异常发生时更直观的定位到根本原因。我们不是想在分析阶段偷懒,而是让数据服务的计算逻辑更简单直接,让大数据处理的时效性更高,报警后人工分析修复问题也更高效.2.3异常收集协议为了支持通用异常和业务自定义异常,需要设计统一的传输和存储数据协议。传输和存储协议的设计:传输协议的设计需要遵循以下原则:顶层模式是稳定的,业务信息是可扩展的。另外,下游数据处理模块的快速连接也是其设计需要考虑的因素,所以我们定义整体数据传输协议格式如下:processingmodule结构体,比较重要的字段如下。其中,元字段是广告托管业务中的一些常用数据字段。此外,meta中的extra字段被第二次打开。通过嵌入sdk暴露的API,开发者可以将其他一些业务数据一起上传,辅助后期排查异常。{exception://存储异常堆栈相关信息request://存储当前页面相关信息meta:{xxx://业务相关字段extra:{//开发者可以自行扩展的字段}}}以上设计在满足稳定性和完备性的前提下,还支持灵活的业务扩展。这里大家可能会有疑问,为什么meta中extrafields不能扁平化?之所以这样设计,与底层表结构索引的建立和下游数据处理的复杂度密切相关。放置在元数据中的业务相关字段是托管页面的通用字段。为了方便后续查询,它们在数据库中以列的形式存在,需要提前枚举出来。为了定义这些一级键,梳理了整体的业务。一级key中最终定义的字段的特点是:业务中用于归因分析的id字段由extra字段表示,辅助信息排查字段存放在meta一级key中。数据升级成本比较高,需要上传下游都明白它的业务意义,然后统一升级。为了方便业务方灵活扩展信息,在meta中增加了一个额外的字段,以字符串的形式存入数据库。数据库存储采用百度自研BaikalDB(https://github.com/baidu/Baik...),对此类结构化信息的实时存储和读取有很好的支持。为了实现普通异常和自定义异常的上报,我们对异常进行了分类捕获。常见的异常报告方式如下:window.addEventListener('error',error=>{logSdk.addWindowErrorLog(error)},true)//借助vue框架,将运行时的js异常报告给Vue.config.errorHandler=(err,vm,info)=>{logSdk.addCustomErrorLog({errorKey:xx,//框架收集到的异常会有一个默认的errorKey。error:err,userExtra:{message:info}});};业务自定义异常的上报方式如下:try{xxxbusinesslogic}catch(e){logSdk.addCustomErrorLog({errorKey:'xxx',//具体业务类型error:e,userExtra:{//业务自定义扩展字段.对应传输协议中的extra}})}页面打开时,通过传入业务元信息实例化内嵌sdk。当异常发生时,如果业务方捕获异常,业务方会自己构造一个错误参数,调用API上报。业务没有捕获到的异常会通过框架统一的异常处理逻辑上报。同时注册全局错误事件处理方法,捕获一些资源加载异常和一些其他异常。这样一种层层嵌套的方式,既实现了业务方定义异常的需求,又可以收集并上报业务方没有捕获到的异常。异常日志入库前需要对数据进行预处理。这里借助公司内部的流计算平台,对每条日志数据进行实时的ETL处理,最终将meta中的数据和nginx层获取的部分数据实时存入数据库.这样,在综合考虑了传输协议的通用性和存储查询的效率后,最终得到了一个用于存储在线异常日志的表。这个存放异常数据的表结构列非常大,是一个很大的“宽表”,这个“宽表”为后续的数据聚合告警提供了数据支持。3.完善监控告警要从大量的数据中生成准确高效的告警,需要经过下图的流程:根据管理的元数据创建监控项;根据监控项的统计信息设置告警策略。3.1划定监控项上面提到,监控平台收集异常,形成一个大宽表。基于宽表,对多个聚合项进行条件分析,可以满足大部分监控项的需求。监控项:过滤某列数据,如URL包含某条query,业务类型属于某范围。平台支持如下图所示的多种过滤条件(支持正则性)。监控聚合:多个监控项的交集。如(业务线===XXX)&&(请求状态码===500)。3.2制定告警策略告警策略包含三个关键因素:聚合周期、告警接收组和触发机制。聚合期监控项是一个统计规则,聚合期是规则统计的窗口。根据数据量、重要性等设置合理的聚合周期,例如对质量最高、对波动敏感的广告转化相关异常设置30秒准实时报警;反之,窗口可适当放大。避免监测波动较大的项目频繁误报。有两种触发方式:阈值和波动。对于比较稳定的异常数可以设置阈值。比如一些业务指标日内基本持平;有规律的波动,异常的数量会跟着波动。如下图,波动比较大,没有明显的时间规律,适合使用阈值:如下图,异常数有时间规律,波动告警可以设置。在实践过程中,我们经常采用双管齐下的方法。平衡报警的准确性并不是一件容易的事。我们也会一边观察一边管理,一边调整参数。更多的挑战和解决方案后面会提到。报警接收组确保报警通过邮件、即时通讯、短信等方式到达。最重要的教训是报警接收者千万不要依赖单点!3.3挑战如前所述,异常监测告警的准确性非常重要,挑战也很大。一般Server服务都有网关,运行环境稳定,而前端代码的运行环境会比较不可控,这对完善监控是一个很大的挑战。挑战一:如何建立完善的监控?报告异常后,必须感知每种类型的异常。通常根据异常类型(资源加载异常、API异常、JS执行异常)进行分类,对每一类异常都建立监控,满足完备性。要求。但是在实践中发现这种设置并不适合托管页面。正如文章开头提到的,托管页面的业务场景涵盖了不同的端点。不同端前流量差异较大,不同端之间复用同一个异常监控项。小流量端产生的错误很容易被淹没在整体异常中。因此,从托管业务出发,将异常分为异常类型和异常所在端两个维度。结合这两个维度建立的监控项,既能满足完备性要求,又能及时发现不同端之间的问题。挑战二:如何提高报警的准确率?托管页面的告警根据各种情况实时聚合,然后与预设阈值进行比较,判断是否触发告警。从理论上讲,告警的准确性取决于业务方。只要聚合条件足够准确,告警就足够准确。但这是成本和实践的反复试验。在触发警报之前,您不知道什么条件可以消除这种无效警报。因此,在业务的不断实践和探索中,沉淀了一些通用的异常聚合条件,以提高告警的准确性:排除爬虫流量(通过ua),只看商业流量(通过商业投放参数判断),逐步完善异常黑名单(已知无法解决的异常,如外部注入导致的“脚本错误”)例如:从某行开头设置JS异常告警。聚合条件设置如下业务线=xxx&&错误类型=js异常优化后的告警聚合条件为:业务线=xxx&&错误类型=js异常&&商业流量flag!=''//排除非商业流量&&uanotlike'Crawlerua'//排除爬虫流量&&error_messagenotlike'Scripterror'//排除黑名单中的异常为了避免对每个告警项重复设置相同的聚合条件,一些公共数据放在最前面处理级别过滤,提高了告警的准确性,减少了各业务方的配置工作。挑战三:对于周期性明显的异常监控项,如何设置同比和环比监控?对于周期性明显的异常监控,一般在初始设置时比较谨慎。如果设置太小,会产生很多无效告警。设置过大,有报警时不能及时触发。实践中发现过这种情况:链率的设置一开始不宜设置,应观察一段时间后再设置。例如,与昨天的数据相比,阈值设置应至少在数据积累2天后设置,并根据实际每天的数据波动情况进行合理的阈值百分比设置。同比设置不能一成不变,要时不时更新一下。随着业务的发展,线上异常请求也在不断变化。如果你发现一段时间内告警比较多,经过排查,发现大部分都是无效告警。这时,您需要重新考虑您的闹钟设置。是否合理。挑战四:报警后如何监控问题跟进?托管页面的异常管理不仅仅是基础能力建设。也希望形成异常问题发现、异常跟进、异常解决的工程能力闭环。异常跟进打通公司内部任务管理平台,为每一个告警建立任务卡,并跟进异常的具体负责人。当问题解决后,可以在任务卡上录入具体信息,达到每一个异常都有专人跟进处理的目的。为了提高问题跟进率,我们还会根据任务卡的信息进行例行统计,分析计算卡停留时长、卡数等,并打通内部即时通讯工具定期推送??卡片统计信息。===4.异常排查接到异常告警后,快速定位告警原因是一种很常见的业务场景。我们从实践中总结出几种快速提高异常排查效率的方法:善用聚合。很多情况下,线上异常数据会在一定范围内来回波动。当突然出现尖峰时,可以通过聚合快速查询问题。通过这些聚合条件,可以快速找到这些意外异常的相似性。常用的聚合选项可以包括ip、ua、deviceid、url等。例如:在一次在线资源加载失败告警中,发现异常日志中的页面url、资源失败url、投递参数都不一样.排除了个别广告页面增加流量的可能,也排除了机器脚本刷页的可能。最后经过ip聚合,发现异常都在某个区域。经与CDN同学反馈,发现该区域存在网络故障,及时推广避免了更大范围的损失。完善对基础能力的支持目前在线JS压缩。异常信息一旦产生,压缩后的信息也保存在异常栈中,不方便排查。因此,我们与下游错误分析平台合作,上传托管页面相关的sourcemap资源。这样生成的JS执行异常中的错误信息可以通过sourcemap文件直接定位到原错误文件所在的位置,方便开发者快速定位到出现问题的代码位置,提高效率的故障排除。5.异常管理上述的异常收集、异常告警、异常排查都是针对被动场景。只有触发在线报警,才会涉及问题的排查。但实际上,除了对这些偶发性秒杀引起的告警问题进行跟进外,我们也主动针对一些现有的线上异常,探索了一些通用的解决方案,积极优化线上异常场景,提高稳定性托管页面在线性爱。首先,为了统一治理目标,配合各方共同应对线上异常,我们从以下几步入手,制定线上异常治理目标:1.明确JS执行异常、API异常错误类型异常,资源加载异常在此基础上再细分。最后是4种异常类型,分别是:JS执行异常、API异常、图片资源加载异常、SCRIPT资源加载异常2.清洗数据。由于托管着陆页的运行场景不同,为了排除一些测试数据或网络爬虫数据的影响,在数据中进行了过滤,只看到来自商业流量的错误。对于一些网上已知的异常错误信息,由于在终端注入,不影响前端稳定性,建立错误信息黑名单,通过特定的错误信息排除此类错误的干扰。3.建立合适的数据标准为了抹平不同产品线之间的流量差异,我们提出了单次广告点击产生异常数的概念。将异常量的绝对值转化为基于广告流量的相对值,从而衡量不同流量产品线的异常量。这样归一化后,排除了广告流量对异常数据量的影响。与网络状况相关的指标,如单次广告点击image/SCRIPT加载失败次数、API请求失败次数等,建立数据标准的过程是:给定参考时间段,计算不同产品的单次广告点击次数参考时间范围内每天出现Image/SCRIPT加载失败和API请求失败的行数,并给出基准时间内第80个百分位值最小的第80个百分位值作为优化目标。(可根据业务本身调整)核心思想:此类异常是网络原因造成的,不同产品线的价值应该趋于一致。以此为基础,将第80个百分位值作为优化的基线。没有达到这个基线的产品线一定是除了网络因素之外还有其他问题,这些产品线可以推广到这个统一的标准。(为了避免某一天的数据过于极端,可以考虑取平均值或者去除尖峰数据取最小值得到最终的目标值)对于与运行时间密切相关的指标:单次广告点击失败次数JS建立数据标准过程是:给出参考时间段,根据errorKey,errorMessage聚合,从大到小排序。在结果中发现是托管页面本身的JS执行导致的异常。这些异常是期望优化到0的异常。排除这些最终值,除以落地页的流量,得到一天单次广告点击的JS执行异常数据的数据。以基准时间内的最小值作为优化目标。(可根据业务自身调整)优化目标确立后,可以进行针对性的优化。针对网络原因导致的资源加载异常核心采用以下思路。使用CDN链接或减少资源大小可以降低首次加载图像的失败率。使用CDN链接图片合理压缩或使用压缩比更高的图片格式(如webp等)再尝试,降低最终资源加载失败率。我们针对APIRequest异常、SCRIPT加载异常、图片加载异常分别从底层入手,并建立相应的重试机制。其中,除了SCRIPT加载异常重试,业务方无感知,API请求异常和图片加载异常,业务方可以通过传入相关参数来表达业务,以支持不同的业务场景。针对JS执行过程中出现的异常,我们建立了完整的处理流程:从通用的异常监控中发现业务可优化的异常;通过细化具体监控条件,对本次业务异常建立单独监控;启动优化方案,处理此类异常;观察监控数据下降是否符合预期。通过以上四个步骤对每一类具体的业务异常进行优化。通过以上方法,我们设定了合适的目标,进行了有针对性的优化。最终,经过针对性治理,各项异常指标的数据均有不同程度的下降。同时引入线上实验,衡量减少线上异常数量对广告转化的影响。实验结果表明:应用下载量和潜在客户转化率都有所提高。六、结语反常治理是一条艰难但正确的道路。在业务实施的实践中,我们遇到了很多问题和挑战。我们完成了从0到1的过程,探索了一种可持续的前端异常监控和治理方法。但是,为了不断减少托管页面前端的异常数量,提高托管页面在线的稳定性,还有很多东西需要培养。----------END----------百度极客说,百度官方技术公众号上线啦!技术干货·行业资讯·在线沙龙·行业会议招聘信息·介绍资料·技术书籍·百度周边欢迎各位同学关注
