聊聊前端日志库在SaaS产品中的应用与设计

MCtalk 技术文章/2021.02.14 文|元三 网易智企资深前端架构师
导读:
笔者所在的公司业务主要是为企业提供全流程的企业服务和一整套SaaS解决方案。对于企业服务SaaS产品来说,客户完成购买并不意味着产品价值已经完全交付,因为客户在首次购买产品时,往往需要经过一系列培训并使用后,才能真正产生价值。因此从本质上来看帮助客户解决在使用过程中出现的问题是B端产品中提供的有偿服务,是产品价值链条中非常重要的一环。
本文将着重介绍开发者排查客户反馈问题这个场景下前端日志库的应用,以及如何设计开发适用于此类场景的前端日志库。
SaaS业务下前端面临的挑战
笔者以前是做C端业务出身,天然带着C端业务的思维,觉得前端把产品交互体验做到极致就够了。当我这个做法套用到做SaaS业务上,着实吃了不少亏。B端产品和C端产品在付费方式的差异、购买决策人和实际使用者不同、产品用途不同(B端客户购买产品的根本原因是为了帮助企业赚钱,C端产品购买决策有可能只是一时冲动好玩),导致SaaS企业经营关注的指标和C端产品存在较大差异,间接导致了对研发侧的导向不同。C端业务前端在研发资源投入上可能为了用户体验不计成本,优化网页性能以提升用户粘性,表现在移动互联网、电商等行业往往关注DAU、MAU、GMV等量级指标。
B端产品的核心关切大部分是能否帮助客户提升效率,产品能否帮助客户达成他的工作目标、是否能帮他快速达成目标比产品界面是否美观重要得多。衡量一家优秀的SaaS企业有一项比较重要的指标——NDR(收入留存率),对SaaS业务下前端开发来说,首要解决的挑战来自如何通过软件研发工作去提升产品易用性、任务效率、服务效率等指标,从而为企业带来提高NDR的分子(存量客户的续费+增购)的效果。
>B端和C端对比
_ | B 端 | C 端 |
用户场景 | 清晰的目的,帮助企业提升效率和质量。 | 用户目的不清晰,主要是为了愉悦和消磨时间。 |
页面交互方式 | 操作简便,信息简洁,有娱乐性、社交性。 | 流程严谨、低风险、高效率。 |
常见付费方式 | 按年预付 | 免费 |
常用经营指标 | NDR、CAC | DAU、MAU、GMV |
值得一说的是,在这方面,《云计算软件产品使用体验质量 度量模型及度量方法》也提出5项指标维度用于衡量产品使用体验,非常具有参考性。这些指标维度包括易用性、任务效率、满意度、一致性、页面性能。其中易用性包括易操作性、易学性、清晰性,任务效率包括功能利用率、任务完成率、任务完成耗时。基于SaaS产品收入可持续性的考量,SaaS企业的目标之一是提高依靠软件产品输出价值的比重,降低依靠人工服务输出价值的比重,因为只有软件产品输出价值边际成本最低,才能不断提升产品服务效率。在这一点上,纯粹依靠人工服务终归是边际成本非常高的,因此在SaaS业务场景下依靠技术创新去提升解决问题的效率是前端能够提供的非常大的产品价值。
如何解决客户反馈问题
售前咨询类。其中开发者主要关注的是问题报障类,也存在一些技术支持回答不了的功能咨询类问题会流向开发者,针对这类问题一般可以采用建设内部的问题排查系统来解决。其中前端开发者主要遇到的反馈问题既有来自于SDK接入这一类的咨询,也有客户认为产品功能不符合预期的问题报告。
针对此,前端为了有效且快速定位这些问题原因,一方面可以在客户端打日志并上报到问题排查系统之中,另一方面,对于S类A类客户(基于SaaS企业针对客户企业规模的分层模型)的紧急问题,如有必要可以迅速和客户沟通,使用远程协助之类的工具在客户的设备中复现并定位问题原因。对于后者,我们设计开发了基于Chrome浏览器Chrome DevTools协议的远程调试解决方案woodpecker-remote,它能够支持网站开发者对网站用户的Chrome浏览器直接进行远程调试。对于前者来说,我们设计开发了前端日志库woodpecker-log以支持将客户端运行状态等信息进行持久化存储供开发者调试排查问题。
前端日志的概念
这里先介绍一下前端日志的概念。通常来说一般在后端开发时经常会听到日志的概念,对后端来说日志是指一种用于记录服务端启动、运行状态的文件。这里的前端日志指的也是用于记录客户端运行状态在客户端存储或者上传到服务器存储形成的日志文件。一般前端在开发、测试环境使用Console记录运行状态就够了,但在生产环境就需要将客户端日志信息发送到服务端存储起来,方便日后排查定位用户反馈问题时使用。
传统的前端日志存在的问题及挑战
-
前端日志库普及率不高,感知差,和异常、性能监控等各自林立。
-
打日志不规范,各种日志格式乱象丛生。
-
日志库本身占用前端性能预算,在性能方面需要考虑尽量减少对JS主线程占用开销,保障主线程尽量空闲。一部分前端日志库在服务器端存储日志,日志产生后需要即时上传到服务器,需占用带宽,并挤占浏览器同域名请求最大并发数,从而拖慢正常业务Ajax请求,需要平衡上报频率和每次上报日志大小。
-
大型应用(如千万、亿级用户数的应用)容易产生巨量日志,日志与Bug之间存在的关联度低,检索和分析操作成本高,如使用Elasticsearch存储海量日志,kibana查询效率低。
-
日志开发及部署不方便,这里有一些问题,前端开发者需要提前在代码中预埋打印日志的代码。否则,当需要定位问题的时候不存在相关的日志无法进行分析。
-
日志库缺少问题的上下文,无法对一个完整的会话进行追踪。如不支持记录用户访问页面发生问题前后的用户界面交互操作记录,以及页面跳转等行为轨迹。
-
问题反馈链路长,如果不能和产品深度集成则容易在反馈链路中间丢失线索,造成沟通成本高企,解决办法可以是在客户上报问题时自动带上当前的日志信息,关联内部的工单、Jira等问题反馈解决系统。
基于B端业务的前端日志库设计
上述问题中,首要解决的是日志和用户反馈问题相关度的问题。核心思路是使用客户端进行日志存储,在发生问题时由用户或者程序发现进行主动上报,而不是定时定频率上报到服务器。这里留两个问题:用户如何发现问题?程序(员)如何发现问题?
另外,性能问题和JS异常也是产生客户反馈的问题来源之一,但从日常SaaS业务的客户反馈问题来源统计来看,这两块并不是主要来源。另外的JS异常监控、性能监控两个领域已经有比较成熟的前端基建支持。因此,非JS异常和性能问题导致的客户反馈问题是前端日志库主要覆盖并解决的问题。
> 一些典型的需要记录前端日志的场景
首先在开始设计之前,先思考一下,前端会如何使用日志库。有这些典型场景可能需要前端记录日志。
-
调用第三方服务,做最坏打算如果第三方服务不可用怎么办。
-
性能预算很低、对性能非常敏感的页面,需要上报一些性能数据。
-
需要重点监控的网页核心流程,使用JS断言结果为false时,需要记录断言失败原因。
对第3种场景,这里简单列了在程序断言为false时使用前端日志库记录日志的Demo:
> 日志库SDK的可维护性
相比于几千行代码在单一文件内维护,将SDK独立成项目并采取前端工程化方式开发更具备可维护性。前端工程化是指采用模块化、组件化、规范化、自动化的技术方案从软件工程的角度解决工程的质量、可维护性问题。这里列举了一些关键技术选型:
- 编程语言
对于SDK相对底层的代码来说,Typescript语言天然提供的类型文档具备可读性和易读性,静态类型检查可以帮助框架或库的使用者在代码运行阶段之前发现错误,智能语法感知可以提供有用的API类型提示。
- 构建
需要考虑为SDK的使用者提供多种JS模块规范的支持,以rollup为例,配置如下:
- 自动化测试
在开发阶段对SDK的自动化测试主要关注单元测试和集成测试。单元测试是用于对模块、函数或类进行正确性检验,可以采用jest框架。值得注意的是,对indexedDB存储和查询进行单元测试时需要模拟数据库,可以使用fake-indexeddb来Mock。集成测试的目的是将系统之间的各个模块组装起来并使用真正的外部依赖、访问真正的indexedDB数据库对代码进行正确性检验。此例中我们采用了Karma + Mocha + chai的方案,对ChromeHeadless、FirefoxHeadless、Safari浏览器进行测试。
- 版本控制
基于语义化版本规范semver进行版本控制。
- Demo和文档
> 使用indexedDB在客户端存储日志
localStorage适合对少量数据进行key-value存储,在客户端日志存储的场景中使用indexedDB更加合适,它具备以下优点:
-
存储和查询结构化数据,支持二进制
-
支持事务
-
异步
-
处理大量数据
- 假定使用10M容量,300bytes的日志,可以存34952条;最长支持循环录制8天日均4369条。
> 性能开销
-
网络性能(延迟、请求失败率)——日志长度、请求体积
-
使用独立域名服务器处理日志请求 Chrome对同一域名的并发最大连接数限
-
DNS预获取 dns-prefetch
-
日志存储前进行字符串压缩
- 使用基于Gzip算法的JS实现LZMA-JS, 实测Level6,300bytes,压缩率79%
-
sendBeacon
- 数据可靠异步传输,并且不会延迟页面的卸载或影响下一导航的载入
-
合并请求
- 合并多个小体积的日志分页上报,单页约1M
-
HTTP/2头部压缩
-
-
运行性能
- 全异步非阻塞式操作,存储、检索、上报
- 维护存储队列支持批量日志存储操作
> API设计
>> SDK初始化设置
>> 实例方法
> 线上出现问题,但是代码中没有打日志怎么办
我们常常需要发布前就在代码中设计好业务关键流程执行时需要打印的日志。否则,当我们需要定位问题的时候,才发现自己并没有输出相关的日志,这样就会比较被动。此时只能临时改代码加日志,重新发布。有没有一种方案,可以在遇到问题的时候,再去代码中相应位置加日志,用户执行改业务流程时就能立刻打印出相关日志,而不用重新走一遍发布流程。这里介绍一种在woodpecker-proxy中的实现,借助MutationObserver接口监听script插入DOM事件,改写浏览器JS请求,将其代理到目标服务器,从而实现在目标服务器修改JS加入日志代码即可在用户浏览器记录日志。
> 日志规范——分级别、分应用打印日志
遵循良好规范记录的日志,有利于排查问题时能够快速根据信息级别、应用进行日志筛选。
>> 分级别
>> 分应用
由于客户端存储受同源限制,日志访问只能在自身域名下进行。多个应用可能会在同一域名下记录日志,区分应用名进行存储易于隔离不同应用的日志信息。
> 问题的上下文需要收集哪些信息
- 设备、浏览器、页面信息
- 关联一次会话的用户界面交互操作
- 关联一次会话的页面跳转
> 如何在收到客户反馈时快速找到相关日志
-
将用户id、会话id存储到日志中并建立索引。
-
将日志上报功能集成到SaaS应用,在客户反馈问题时自动查询当前会话日志并上报。
-
在客户反馈问题时将用户id、会话id写入到内部的工单系统,提Bug时带到Jira系统。
未来努力的方向
-
加强可靠性
- 网页崩溃时如何保障前端日志库依然正常工作,记录此时的日志?使用Service Worker监控网页崩溃。
-
更直观的问题上下文环境
- 采用浏览器录屏方案录制出现问题前后的用户界面。
-
更友好的客户通知和告警
- 使用Notification桌面通知。