企点电话SDK是我们对外提供电话服务的一种重要方式,我们的客户只需要在页面上引入企点电话SDK,就能在网页上轻松的实现电话接打的能力。目前我们日外呼通话时长峰值已达百万分钟级,而电话功能对服务稳定性要求非常高,运维压力大。一通呼叫的失败,可能的原因有多种多样,我们如何在客户报障后做到快速响应、定位解决问题并且给到客户反馈,这是我们提供电话服务首先要面对的问题,也是我们的客户愿意信赖我们提供电话服务能力的一个重要参考方面。
排查问题常见的方法不外乎有如下几种:
我们在企点电话SDK中实现了一套日志追踪系统追客(Tracker),既弥补了方式一日志可读性差的问题,又同时做到了方式二1:1直观的还原用户现场的能力,问题排查的整个过程会比方式三亲临问题现场还更便捷高效,下面我介绍下系统的具体实现。
追客系统主要实现了日志分类记录、存储、远程查看等三部分功能。
依赖console接口的染色输出能力,追客实现了按颜色区分的分类线索日志,console.log接口输出格式如下:
console.log('%c日志内容 %c2021-12-27', 'color: green;', 'color: red;background-color: yellow;');
追客系统里输出的日志信息样例如下:
在打印日志的时候,我们倡导把日志按线索分类,每类日志分别创建一个实例,每个实例都可以在创建的时候指定颜色,不指定的话会随机生成一种颜色,通过这种颜色的区分,很容易能找到对应某一类线索的日志信息,如上图所示。实例化过程如下:
let t1 = new T(options);
let t2 = new T('labelName'); // 最快捷简便的方式
t1.$log('msg1'); // 对应颜色1
t2.$log('msg2'); // 对应颜色2
除了日志内容,样例图里还可以看到,实例名称的后面会带上一个数字,这是所有实例每次调用自动累加的,有总的计数和每个实例单独的计数,方便开发调试阶段查看观察日志记录,在远程查看日志的时候,也能根据这个序号知道中间是否有日志没有上报成功,还能区分出是否有多个页面在同时上报到一个ID。
可以看到日志的颜色是作为背景色呈现的,所以在随机生成颜色的时候,必须保证颜色亮度不能太亮,不然实例名称会看不清楚,这里是通过hsl可以很好的做到:
export const getRandomColor = function (dark) {
let hue = Math.floor(Math.random() * 360);
return `hsl(${hue}, 100%, ${dark ? 30 : 60}%)`;
};
我们做了一套事件系统,很好的实现了插件机制,业务方可以选择性的使用特定的能力,使用方式如下:
import pluginName from '@tencent/Tracker/path/to/plugin';
T.$use(pluginName, {
// options
});
$use具体做的事情,其实就是事件绑定,每次log记录都会同时抛出log事件,这样每条log信息在插件里都能收到,所以可以很方便的对这些log信息补充一些额外的能力,如决定是否对这些日志做本地存储,是否上报服务端等等。
目前实现的插件功能有:
我们引入了一个第三方的本地数据存储插件实现了这个功能,可以很方便的把数据存储在websql、indexedDB或localstorage等浏览器的本地存储里,同时可以设置本地数据保存的有效时长清理过期数据,提供对外接口读取本地存储数据等。
我们实现了一个可以99%还原console面板输出日志效果的日志查看插件,可以做到日志信息跟console面板里实时同步,可以自定义一种快捷方式来唤起这个日志查看界面,针对手机端的h5页面或者hybird页面的debug会很有效果;远程查看日志的服务里也是直接调用这个插件来显示用户端的日志信息,跟在用户现场看到的日志信息几乎是一样的,而且还增加了日志按多个维度筛选的能力,大大方便了日志的阅读和线索的发现。
日志在打印的同时,我们实现了这个插件来近乎实时的把日志数据上报到服务端,在数据上报的时候为了避免并发量太大做了缓冲处理,目前可以做到从用户端输出日志到服务端能拉取到日志只需延迟2-3秒的时间,日志上报的成功率可以达到6个9,这块内容下面在日志的服务端存储部分会详细描述。
另外我们利用服务端的EventPush技术还实现了日志的单点拉取,避免因用户端关闭了日志上报而无法排查问题。启用了logReport后就会自动向EventPush Server订阅流,具体的实现方案如下:
电话 SDK 中的线索日志以还原用户现场为目标,因此日志中除了记录行为操作数据,还需要记录上下文情况,HTTP / Websocket 的请求和响应等等,相对于数据埋点中的日志,电话SDK日志单条长度更长、日志的内容也更多样。在查询日志时,会利用多种条件进行筛选,还需要支持准实时的检索能力。相对来讲,对日志的保存时间要求并不长,几天到几周即可,也没有复杂的聚合和分析需求,日志入库后,一般不会对其进行二次处理和再分析,根据日志的查询需求和空间占用情况,定期进行清理即可。前端收集到日志并提交到日志收集接口后,后续大概会有如下几个步骤:
1、接口收到日志后,立即返回响应,并将收到的日志推入消息队列;
2、从消息队列中消费消息,对日志进行清洗、数据整理等操作;
3、将整理后的日志持久化;
4、提供日志的实时查询和统计服务,查询需要支持按字段检索和全文检索。
对于常规的日志收集系统,主要可以分为接口层、日志处理层、日志存储层、数据展示层四大部分。
接口层需要考虑日志的高并发、上传量大的特点,在确保接口稳定的前提下,提升接口返回速度,保证日志的完整性。通常情况下,这一层会加入负载均衡系统,根据一定的策略将日志分发给接口层服务,接口层服务一般不处理日志,只将日志转换为统一格式的消息,推入到消息队列。利用消息队列对日志进行缓存和分发,对日志生产进行消峰,只要消息积压不太严重,一般不需要调整接口层服务的数量。
日志处理层中会对日志进行解析,保证日志的结构正确,对于截断或损坏的日志进行单独的记录。由于电话 SDK 会有多个版本,不同版本之间提交的日志会不一样,有的字段的值类型不一样,有的同一字段位于日志 JSON 中的不同位置。都需要在这一步对日志格式化,做一些类型和含义上的转换,补充缺失的字段等。日志处理层通常是从消息队列中订阅和消费消息,进行上述的处理后,将日志存储到日志存储层。
日志存储层提供日志的存储、查询和聚合。一般日志按时间存储,即可便于查询,又能方便管理。例如 Elasticsearch 可以按时间创建索引,查询时可以根据时间来精确查询范围,提升查询的效率,也能方便快捷的删除过期的日志。
数据展示层主要是提供日志的查询接口,有些日志系统还需要 dashboard,提供对日志收集情况、日志本身的统计和分析能力,比如 Kibana 就提供了对 Elasticsearch 中的数据可视化、查询、索引管理、节点状态管理等功能。
根据对日志规模的初步估算,预计每天日志数量在千万级别,空间消耗数十G,对于主流的日志解决方案,基本都能满足需求,以 ELK 方案为例,可以使用 nginx 作为负载均衡,将请求转发到 Kafka 消息队列,用 Logstash 消费消息,处理数据,转存至 Elasticsearch。再构建一个服务,专门从 Elasticsearch 中查询日志。
虽然 ELK + Kafka 的方案可以满足我们的需求,但是这个方案还是需要搭建多个服务,并且可能还涉及到服务的扩容问题。经调研发现,腾讯云提供的云原生生态,也可以实现上述构架的相关功能。
云原生(Cloud+Native)是一套基于云来构建和运行服务的方法、体系、工具和服务,从设计之初就考虑利用云上的各种资源和服务,实现弹性伸缩的分布式架构,云原生代表性的技术包括微服务、容器化、DevOps等。
云原生架构非常适合日志系统。弹性伸缩可以轻松应对日志收集时的波峰问题,消息队列、日志处理、数据存储之间也是典型的分布式架构,利用云上的服务,通过简单的配置即可实现负载均衡、消息队列、数据存储这几大部分,只需要将精力集中在编写日志清洗和处理的代码,后期几乎没有运维成本,利用云原生的监控功能,可以方便的了解系统的整体运行状态。
Serverless Computing 无服务器计算是在 PaaS 的基础之上构建的一种代码执行平台,也叫Function as a Service,函数即服务。对于平台的使用方而言,不需要关注服务器的细节,只需要编写代码块,处理函数的输入和输出,即可直接使用云平台的计算资源。阿里云提供的函数计算,腾讯云和华为云提供的云函数,都是 Serverless 概念的实现。比如腾讯云的云函数服务,提供了一整套的开发工具,在开发环境中就可以直接编写和提交代码到云函数中,支持使用 Node、Python、Go、PHP、Java 编写代码。云函数根据函数的运行时间计费,通过设置最大并发数量,可以提升波峰的处理能力。
云函数本身并不能直接对外提供 http 服务,需要绑定到 API 网关才可以。API 网关可以实现对接口测试、预发布、发布的全生命周期管理,还提供了文档生成、测试、访问日志和监控、流量与权限控制等功能。API 网关本身部署在分布式集群上,可以承载较大的访问量,结合云函数,可以实现弹性伸缩。
借助云上已有的消息队列、Elasticsearch 服务,使用云函数处理请求、转换日志、存储日志,利用 API 网关对外提供日志提交接口,定时触发云函数来管理 Elasticsearch 的索引。
为了保证单个云函数的可维护性,一个云函数一般只完成一个功能,我们需要创建如下几个云函数:
触发方式为 API 网关,处理外部提交的日志,解析出请求中的日志数据,检查日志格式,将日志组合为 Kafka 的消息,并推送到消息队列。API 网关会根据当前接口的请求情况,动态增加云函数的实例数量,直到达到配额上限。
触发方式为 Kafka ,当有新消息时会调用云函数,检查消息中的日志数据,根据具体规则进行格式化和转换处理,通过 Elasticsearch 的 API 接口来保存数据,为了提高效率,可以接收多个 Kafka 消息后,再合并多条日志,通过 Elasticsearch 的 Bulk 操作批量存储日志。本云函数在系统中较为重要,当出现日志格式问题、转换发生异常,或者存储出现错误时,可以触发警告。
触发方式为 API 网关,供 logViewer 插件日志展示时使用。处理日志的查询,主要是构造 Elasticsearch 的 DML 查询语句,再将结果作为接口响应返回。
定时触发,通过 Elasticsearch 的接口,查询当前的磁盘占用情况,超过一定比例时,根据索引名字中的时间,删除最早的索引,释放磁盘空间。
基于云原生的日志收集架构,无需传统的负载均衡,可以自动根据并发数量弹性伸缩计算资源,各服务基本也是按使用情况计费,无需专门的维护人员,通过云平台提供的监控工具,可以直接查看系统的处理情况,能够低成本的满足我们的日志收集需求。
这是我们实现的日志拉取入口,显示查询结果使用的是追客的logViewer插件,目前已经可以做到只需要用户告诉我们一个大概的时间和当时外呼的号码就能快速排查出问题。我们把电话日志分成了企点主号、工号和通话callid三个维度,主号维度获取的日志是企业内所有工号的行为记录,所以我们能为每一个客户提供独立的查询页面,方便他们自己的开发去查看问题;企点工号维度查询到的日志,是记录了这个工号从登陆到关闭页面期间的所有电话行为记录;callid维度的日志是一通电话的整个过程的完整记录。
在企点电话SDK中,我们归纳划分出以下主要分类线索:用户的页面交互、请求的发起和响应、事件推送、电话状态变更、websocket连接状态、webrtc环境检测等6类,完整记录了用户在页面上的交互行为、对应的服务端请求和响应、用户的本地状态等日志,所以我们要可视化的还原复现出用户的侧的行为表现会变得容易很多。依赖上面提到过的工号和通话callid维度的日志查询能力,我们可以方便的还原复现一个坐席或具体某一通电话当时的场景,目前已经可以还原主要的场景,在哪个阶段出现问题前可以暂停播放,打开debug工具打上断点然后继续播放,这样就可以复原用户当时的本地状态进入要排查的逻辑。
12月27号16:05:收到客户反馈坐席断线问题频发,给了其中一个坐席账号28******20;
12月27号16:06:查询日志筛选出warn和error级错误,从他上班到当前时间点,并没有发现掉线的情况,只看到几次ticket过期的响应;
12月27号16:06:怀疑是ticket过期后业务侧没有刷新ticket导致系统不能正常使用,所以取消日志筛选,定位到最近一条1482的过期响应查看他后续的动作记录;
12月27号16:11:经过分析确认,这三次ticket过期的响应后,都没有看到更新ticket接口的调用,从日志累加计数的数字可以看到,每次出现过期响应后,用户侧都是10几秒后就直接刷新了页面,所以可以断定他们的过期续期逻辑出现了问题,给客户做出反馈;
12月27号17:05:客户自查确认是自己线上环境更新ticket的接口出问题了。
依赖追客系统,我们快速排查定位到了问题,整个问题排查的过程可以看到比较高效,因为我们有完整的行为记录,可以给到客户非常明确的反馈,这个案例是客户自身系统出现问题导致的,定位出问题会相对简单,对于一些内部逻辑bug导致的问题,我们会经常使用到可视化的用户现场还原来断点调试排查,这类问题的排查时间会需要长一点,但是可以肯定的是,只要用户那边出现过的现象,理论上都是可以保证能在本地复现出来。
追客设计的初衷并不只是用在运营维护处理线上问题,更多功能还在逐步完善中,它的目标是做到从开发、自测、联调到线上运营全流程的介入,让你的应用只要接入了追客,就能成为稳定、可靠、易追踪、会自动告警的系统,我们正在做以下尝试:
通过追客可以创建出一个对象,或让一个已有对象变得可观察可监测,可以做到自动输出接口调用的入参、返回结果和执行耗时,捕捉接口错误,输出行为的调用链路等。
有了这些能力,后面我们排查问题可能就不用一行一行查看代码逻辑,很多场景下应该都能只需查看这些输出日志就能发现问题点,针对一些重点逻辑还可以设置一些告警策略,在你收到告警的同时就能方便的看到用户端当时的具体情况。
同时我们还会进一步完善可视化还原,丰富日志展示的能力,如增加dashboard显示主要客户的实时用户端运行情况,在有错误发生时就能及时察觉到。