日志采集
信息分类
日常手机的日志信息大概分如下几个类别:
- 错误
错误类型分多种,不同的错误类型对应用的影响程度不同而分为不同的级别。根据这些优先级,我们也需要指定出一套紧急修复的SOP。
错误类别 | 捕获方法 | 表现形式 | 发生概率 |
---|---|---|---|
运行错误 | 全局监听error | ||
类型错误 | 全局监听error | ||
引用错误 | 全局监听error | const a = null; a.b.c = 123; | 频繁 |
接口错误 | 设置ajax和fetch的全量代理 | 根据不同的公司的业务制定 | 频繁 |
网络错误 | 设置ajax和fetch的全量代理 | 捕获http status 4xx、 5xx | 经常 |
资源加载错误 | 全局监听error | http 404 500 timeout | 较少 |
RangeError | 全局监听error | ||
语法错误 | 全局监听error | 语法错误:cont a = "123"; | 较少 |
TypeError | |||
URIError | |||
AggregateError | |||
EvalError |
- 性能
性能监控总的来说分两块:加载性能和运行性能。对于不同的性能我们都有较为成熟的指标和衡量方式去度量。如对于加载性能,我们可以通过window.performance
中的各个参数,计算加载的速度,同时也可以通过performanceOberser
中获取FP FCP 指标从而评估首屏加载的速度。而对于运行中的性能问题,我们能做的较少。一般采用嵌入式埋点,通过对比两个时间的差值来判断某段js造成的性能问题。同时之前performanceOberserver
能够实时监控长任务的执行频率,因此也是总要的工具之一。最后,我们也可以通过mutationOberser
来判断界面元素的变化来判断界面的性能问题。
- 行为
这些统计信息基本上包含了客户的一些行为,如点击,停留,曝光等数据。这些数据对于公司数据团队来说可能是重要的。我们一般采用强行嵌入代码的方式进行统计,但这中方式对代码的侵入性较大。一种方式是通过全局监听document
,然后在标签上做标记的处理。最后将用户的行为发送到服务端。
自定义
- 敏感
有些信息对于用户来说十分重要,需要主要相关的法律责任的情况下对这些信息进行采集。而且在上报的过程中需要做到数据安全防护
- 环境
大多数的用户环境不会有太大的变化,不过了解用户的环境信息可以对我们网站的性能评估有补充,并且分析一定的用户身份。环境信息一般通过日志表查询,http发送请求一般都会把各种信息存在代理服务器的表中,我们可以在这些标中用脚本提取相关信息。
- 身份
了解一个数据来自哪里非常重要,通常,我们采用cookie用来记录用户身份信息,用session来保存某一次回话的id。表示用户的身份和回话身份对于数据分类来说十分重要。
- 业务
业务数据根据不同的产品来说非常不一样,如电商网站经常记录用户的购买记录,浏览历史。停车累死的软件对于车牌 车场 等信息就非常总要,甚至经常配合身份信息去定位信息类别。
上报方式
我们一般用一个像素的透明gif来做数据上报,因为这种方式能最大的较少数据量的发送。而且采用图片资源做传输不需要考虑跨域的问题。很多公司的上报功能就是采用此种方式进行的数据上报的,百度,友盟,阿里等。不过有些场景这种方式无法适应,在统计之后发送了定向跳转行为,因为界面被打断了。为了兼容此种情况,我们可以考虑用navigator.sendBeacon的方式进行上报。sendbeaon
方式可以在用户甚至关闭界面的后还可以继续发送请求。采用的应该是serviceworker
来进行进行底层操作的。但是因为要考虑兼容性问题,我们可以做一个数据回退。将发送方法进行降级处理。
上报频率
如果每个行为或者错误都进行上报的话会对服务器和数据库造成极大的压力。我们可以通过制定一些规则来控制上报的频率。根据优先级的不同从而制定不同的发送频率。当然,这就涉及到客户端的存储,既然选择了不同的瓶绿,势必有些数据需要停留在客户端一段时间,等待发送的时机。下面这张图作为你发送数据的依据作为参考:
数据分类 | 重要性 | 体积 | 效时比 | 存储方式 | 上报频率 |
---|---|---|---|---|---|
错误信息 | 非常重要 | 少 | 10 | 无 | 及时发送 |
性能信息 | 重要 | 少 | 3 | indexdb | 按照版本发布的频率 |
用户信息 | 一般 | 多 | 3 | indexdb | 根据业务需求自定义即可 |
自定义 | 一般 | 较多 | 2 | indexdb | 根据业务有限级别 |
身份信息 | 重要 | 少 | 10 | cookie | 发送一次 |
会话信息 | 重要 | 少 | 10 | session | 以天为单位发送 |
业务信息 | 一般 | 较少 | 8 | indexdb | 根据业务需求自定 |
我们依据重要性,体积,效时比来制定不同的存储方式以及上报频率,重要性不言而喻,是指信息对应用的整体运行产生的影响,例如错误无疑是最重要的,而对于一般的用户行为来说,这些数据就显得不那么重要。另外体积是指单位时间内的数量大小。数据越多,发送的压力就越大。需要做控制。最后是有效期,指的是随着时间而改变的重要程度。例如错误,一定是第一时间上报才价值最大,可以即使止损。而用户行为,对于数据分析的同时来说,及时性就变得不重要。以上只是我们列出来的一个参考指,不同的业务对于上报的数据有可能跟上面的大相径庭。在评估性能是应该把这些纬度考虑在内。
数据关联
信息收集的基本类型需要符合一个大原则即:谁在某什么时候做了什么,以及这么的原因。总结就是3w1h(who what when how);通过一条信息去判断用户的行为无疑是以管窥豹。数据发生既有历史现在,我们可以预测未来。场景复现一般都是线上报错后手忙脚乱的程序员最先需要找到的。有些错误信息并不是避险,所以需要模拟常出现的场景才能定位问题。这种工作问客服或者反馈的用户是非常低效和不明治的。我们需要自己建立数据关联性并且分析历史错误问题,同时也能帮助产品运营同时预测用户的行为。
- 建立
traceid
,每次用户进来都需要一个本次表示,以标示用户的进程如过程的一个总流程。一般用sessionstoreage
来存储,以用户同源的页面操作为分区。建立traceid能够帮助我们在用户纬度继续细分操作场景。 timerid
这个相当于给用户的的行为打上一个时间点,告诉信息的阅读着这个信息在本次行为中的先后顺序,当需要定位红一个时,我们可以推倒出上下问。
有了traceid
和timerid
我们就能准确的了解一个行为的上下文,这对于错误定位来说非常合适。
我们上文提到了用户的操作会被客户端存储,这就会造成错误无法被即使还原。这可以通过错误发生机制,一旦某次操作发生了错误,我们强制把缓存中的对应信息连同错误信息一并发出。避免错误上下文被缓存的问题。
日志存储
存储类别
日志存储的架构设计可以分多种,一种是服务端的,一种是客户端的。
服务端主宰的是日志表系统,通过nginx
网关把所有的http
记录下来,然后定期的通过消息系统去日志标中读取相应的日志信息。这种方式是做到快速即时,不会因为客户端的某些清除缓存操作而丢失信息。不足是前端开发人员需要做更多的提取和存储操作。而且每条信息都发送给服务器的话势必操作服务器的压力。在性能不高的服务架构上去做要比较困难。
客户端主宰的方式如上文提到的,我们利用每台客户机器做日志的存储。相当于是把服务器的压力分个用户的机器上。客户端的好处显而易见,就是减少服务器眼里,根据不同的数据优先级定时定量的进行http发送。不过它的弊端就是无法作答永久存储很多缓存可能会被用户无意的清除调。而且也会存在数据不及时的问题,还有就是你并不会知道,下次用户的这些信息会被发送上来(说不动这个用户只会用一次你的应用也说不准)
你需要根据你自己的业务来决定哪种业务更适合你或者你的公司。或者你可以两者结合使用。来满足你对数据的实施性
数据库
统计信息分类复杂,数量众多。根据这些维度,你可以选择不同数据库进行存储。在存储的时候需要考虑建设合理的表结构以及索引,以提高数据的使用的效率。另外,并非所有的数据需要进行存储,一类数据可以存储在redis中,例如已经大多数的错误都会被解决,这些问题就无需被存储到数据库中占用资源。解决之后可以将其从redis中删除。在处理多种复杂数据的时候最好按照一定的分类程度存储到标中,例如,按年/月/日存储数据可以有效分类,或者省市区都可以有效的归纳和搜索信息。
统计分析
数据搜索
在海量的数据中查找相关的数据需要合理的分布。前面已经讲过通过建立索引,分表等操作减少数据查找的时间,提高响应速度。一般小数量的规模通过sql直接查询不会有太大的问题。大规模数据可应用应用在监控领域比较有代表性的有 时序数据库 OpenTSDB 和 全文检索搜索引擎 Elasticsearch 。
平台建设
搭建统一的监控平台,采用nodejs
作为这种平台最为合适。一时前端开发人员较容易上手,其次是node搭建这种平台又快速的优势。利用express
或者koa
等web框架快速进行搭建。对于大数据来说,可视化无疑更符合人的直觉。因此,采用JavaScript 3d动画库或者利用python
的类库生成大数据屏幕是非常棒的选择。
数据搜索分析是一项浩大的工程,需要深入去研究,此处一笔带过。
监控告警
错误一旦发生,是要立即同时相关人员去定位的。我们在设计监控系统中的一个重大原因就是需要监控并且解决错误。因此,我们需要做一些规则来设定推送告警。
- 导致页面无法正常运行的错误
- 重要的数据达到设定的阈值
- 在一定时间内的某个条件的触发频率较高
- 信息中包含某个设定的关键词时
当条件达到是我们的程序可以选择推送程序进行推送。推送的方式不限,最好是能即时触达到相关人员的方式:最好是微信(手动坏笑)
快速定位
有了错误信息,我们就能即时知道线上出现的问题。但我们上传的脚本都是经过编译压缩的,而且大多数适合为了提升性能,我们一般不会将map文件放到线上去,如何定位问题就成了关键所在。以下是线上代码出现问题时在控制台或者日志中记录的错误:
如果单只看错误信息,是无法定位到错误的。这时候我们需要用到sourcemap来还原错误发生的位置。在node的第三方模块中,有一个source-map
, 它提供为我们还原源代码提供了。 我们把这个包下载下来。
npm install source-map
然后通过错误信息中的行号和列号对信息进行还原,我把还原的功能封装成了一个小模块,这个函数接受三个参数:string 原map文件,int 错误行号,int 错误列号。
const sourceMap = require('source-map');
const readline = require('readline');
const fs = require('fs');
sourceMap.SourceMapConsumer.initialize(
{"lib/mappings.wasm": "https://unpkg.com/[email protected]/lib/mappings.wasm"}
);
module.exports = function(rawSourceMap, line, column) {
return new Promise((reslove, reject) => {
let errorContent = [];
sourceMap.SourceMapConsumer.with(rawSourceMap, null, consumer => {
let originalPosition = consumer.originalPositionFor({
source: "./",
line: +line,
column: +column
});
const sourceContent = consumer.sourceContentFor(originalPosition.source);
const all = sourceContent.split(/\r\n/g);
originalPosition.content = all[originalPosition.line - 1];
reslove(originalPosition)
});
})
}
通过这个模块还原出来的就是你的原代码。我们实际上只需要看到出问题的前后几行,因此,我在函数中使用了文本截断的方式,对外只输出发生错误的上下若干行的信息。你可以这样引用此函数:
const buffer = fs.readFileSync(files[0]);
const mp = sourceMap(String(buffer), result.lineNo, result.columnNo);
第一行去读取的文件就是sourcemap原文件,你每次打包后就需要把这些后缀为.sourcemap的文件传入到服务器上去,这样才能在出错的适合找到对于的源文件,进行快速还原。
为了帮助你快速实现sourcemap文件上传,我写了一个file-ziper-and-uploader的webpack插件,实现自动动上传文件到服务器去。相关文档请在
npm
上查看。
到此,我们就可以把一个线上错误快速还原定位。从报错到定位源码,不会超过一分钟,可以说是非常的迅速。
监控框架
很多事情不必重复造轮子,业界又很多优秀的开源框架提供给我们使用。如果你不是在大厂,我不建议你从零开始搭建前端监控系统。我自己也是在有了一次手动造轮子的经历之后,最终采用sentry搭建的前端监控系统。结果是真香。
sentry
是开源的第三方框架,功能丰富,部署简单,最重要的是免费。betterjs
腾讯的前端监控方案fundebug
第三方监控框架,收费fee
贝壳网的前度监控系统,已经开源。免费- 岳鹰监控 阿里前端监控,收费