采集聚合端(日志服务器)
这个环节,输入是接口接收到的错误记录,输出是有效的数据入库。核心功能需要对数据进行清洗,顺带解决了过多的服务压力。另一个核心功能是对数据进行入库。
总体流程可以看为错误标识 -> 错误过滤 -> 错误接收 -> 错误存储。
错误标识(SDK配合)
聚合之前,我们需要有不同维度标识错误的能力,可以理解为定位单个错误条目,单个错误事件的能力。
单个错误条目
通过date和随机值生成一条对应的错误条目id。
const errorKey = `${+new Date()}@${randomString(8)}`
function randomString(len) {
len = len || 32;
let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
let maxPos = chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
单个错误事件
首先需要有定位同个错误事件(不同用户,发生相同错误类型、错误信息)的能力。
通过message、colno与lineno进行相加计算阿斯克码值,可以生成错误的errorKey。
const eventKey = compressString(String(e.message), String(e.colno) + String(e.lineno))
function compressString(str, key) {
let chars = 'ABCDEFGHJKMNPQRSTWXYZ';
if (!str || !key) {
return 'null';
}
let n = 0,
m = 0;
for (let i = 0; i < str.length; i++) {
n += str[i].charCodeAt();
}
for (let j = 0; j < key.length; j++) {
m += key[j].charCodeAt();
}
let num = n + '' + key[key.length - 1].charCodeAt() + m + str[str.length - 1].charCodeAt();
if(num) {
num = num + chars[num[num.length - 1]];
}
return num;
}
如下图,一个错误事件(事件列表),下属每条即为实际的错误条目。
错误过滤(SDK配合)
域名过滤
过滤本页面script error,可能被webview插入其他js。
我们只关心自己的远端JS问题,因此做了根据本公司域名进行过滤。
// 伪代码
if(!e.filename || !e.filename.match(/^(http|https):\/\/yun./)) return true
重复上报
怎么避免重复的数据上报?根据errorKey来进行缓存,重复的错误避免上报的次数超过阈值。
// 伪代码
const localStorage = window.localStorage;
const TIMES = 6; // 缓存条数
export function setItem(key, repeat) {
if(!key) {
key = 'unknow';
}
if (has(key)) {
const value = getItem(key);
// 核心代码,超过条数,跳出
if (value >= repeat) {
return true;
}
storeStorage[key] = {
value: value + 1,
time: Date.now()
}
} else {
storeStorage[key] = {
value: 1,
time: Date.now()
}
}
return false;
}
错误接收
在处理接收接口的时候,注意流量的控制,这也是后端开发需要投入最多精力的地方,处理高并发的流量。
错误记录
接收端使用Koa,简单的实现了接收及打印到磁盘。
// 伪代码
module.exports = async ctx => {
const { query } = ctx.request;
// 对于字段进行简单check
check([ 'mobile', 'network', 'ip', 'system', 'ua', ......], query);
ctx.type = 'application/json';
ctx.body = { code: '1', msg: '数据上报成功' };
// 进行日志记录到磁盘的代码,根据自己的日志库选择
};
削峰机制
比如每秒设置2000的阈值,然后根据请求量减少上限,定时重置上限。
// 伪代码
// 1000ms
const TICK = 1000;
// 1秒上限为2000
const MAX_LIMIT = 2000;
// 每台服务器请求上限值
let maxLimit = MAX_LIMIT;
/**
* 启动重置函数
*/
const task = () => {
setTimeout(() => {
maxLimit = MAX_LIMIT;
task();
}, TICK);
};
task();
const check = () => {
if (maxLimit <= 0) {
throw new Error('超过上报次数');
}
maxLimit--;
// 执行业务代码。。。
};
采样处理
超过阈值,还可以进行采样收集。
// 只采集 20%
if(Math.random() < 0.2) {
collect(data) // 记录错误信息
}
错误存储
对于打印在了磁盘的日志,我们怎么样才能对于其进行聚合呢,这里得考虑使用存储方案。
一般选择了存储方案后,设置好配置,存储方案就可以通过磁盘定时周期性的获取数据。因此我们需要选择一款存储方案。
对于存储方案,我们对比了日常常见方案,阿里云日志服务 - Log Service(SLS)、ELK(Elastic、Logstash、Kibana)、Hadoop/Hive(将数据存储在 Hadoop,利用 Hive 进行查询) 类方案的对比。
从以下方面进行了对比,最终选择了Log Service,主要考虑为无需搭建,成本低,查询功能满足。
功能项ELK 类系统Hadoop + Hive日志服务
日志延时
1~60 秒
几分钟~数小时
实时
查询延时
小于 1 秒
分钟级
小于 1 秒
查询能力
好
好
好
扩展性
提前预备机器
提前预备机器
秒级 10 倍扩容
成本
较高
较低
很低
日志延时:日志产生后,多久可查询。查询延时:单位时间扫描数据量。查询能力:关键词查询、条件组合查询、模糊查询、数值比较、上下文查询。扩展性:快速应对百倍流量上涨。成本:每 GB 费用。
具体API使用,可查看日志服务[2]。
可视分析端(可视化平台)
这个环节,输入是接口接收到的错误记录,输出是有效的数据入库。核心功能需要对数据进行清洗,顺带解决了过多的服务压力。另一个核心功能是对数据进行入库。
主功能
这部分主要是产品功能的合理设计,做到小而美,具体的怎么聚合,参考阿里云SLS就可以。
首页图表,可选1天、4小时、1小时等等,聚合错误数,根据1天切分24份来聚合。首页列表,聚合选中时间内的数据,展示错误文件、错误key、事件数、错误类型、时间、错误信息。错误详情,事件列表、基本信息、设备信息、设备占比图表(见上面事件列表的图)。
image.png排行榜
刚开始做了待处理错误列表、我的错误列表、已解决列表,错误与人没有绑定关系,过于依赖人为主动,需要每个人主动到平台上处理,效果不佳。
后面通过错误作者排行榜,通过钉钉日报来提醒对应人员处理。紧急错误,通过实时告警来责任到人,后面告警会说。
具体原理:
image.pngSourceMap
利用webpack的hidden-source-map构建。与 source-map 相比少了末尾的注释,但 output 目录下的 index.js.map 没有少。线上环境避免source-map泄露。
webpackJsonp([1],[
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
...
])
// 这里没有生成source-map的链接地址
根据报错文件的url,根据团队内部约定好的目录和规则,定位之前打包上传的sourceMap地址。
const sourcemapUrl = ('xxxfolder/' + url + 'xxxHash' +'.map')
获取上报的line、column、source,利用第三方库sourceMap进行定位。
const sourceMap = require('source-map')
// 根据行数获取源文件行数
const getPosition = async(map, rolno, colno) => {
const consumer = await new sourceMap.SourceMapConsumer(map)
const position = consumer.originalPositionFor({
line: rolno,
column: colno
})
position.content = consumer.sourceContentFor(position.source)
return position
}
感兴趣SourceMap原理的,可以继续深入,SourceMap 与前端异常监控[3]。
错误报警报警设置每条业务线设置自己的阈值、错误时间跨度,报警轮询间隔通过钉钉hook报警到对应的群通过日报形式报出错误作者排行榜
image.png四、扩展行为搜集
通过搜集用户的操作,可以明显发现错误为什么产生。
分类搜集方式点击行为
使用addEventListener监听全局上的click事件,将事件和DOM元素名字收集。与错误信息一起上报。
发送请求
来源:【九爱网址导航www.fuzhukm.com】 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!