浅谈前端异常监控平台实现⽅案
异常捕获是改善软件质量的跟踪⼿段之⼀,常见的⽅式是记录⽇志,从⽇志分析异常问题进⽽跟进。对于前端项⽬来说,异常可能是后端接⼝数据导致,可能是前端本⾝业务逻辑问题导致,不管是什么导致的异常,只要能够精准的捕获到就能够分析出问题所在。可能有⼩伙说有测试阶段,全⾯的测试机制的确能够降低异常的出现,但是测试⼤部份情况是在⾮⽣产环境上进⾏的,覆盖⾯有限。
⽇志是收集异常的最佳⽅式,⼀个异常监控平台就需要包括异常采集、异常存储、异常统计与分析、异常报告、异常告警,⽽对于⼀个通⽤平台来说,就需要项⽬管理、版本管理、团队管理、仓库管理等等。本⽂主要介绍⼀下异常采集需要考虑的问题,并跟⼤家分享两种现成的解决⽅案。
异常介绍
异常,是每种编程语⾔都需要考虑的⼀种结构,如何友好的跟踪异常⽽不影响⽣产环境上的业务,这就需要从项⽬开发到上线整个过程做⼀定的规范。下⾯就来谈谈前端的异常及处理⽅式。
异常分类
先来说说JavaScript的错误类型,ECMA-262 定义了 7 种错误类型,说明如下:
Error:普通异常,通常与 throw 语句和try/catch 语句⼀起使⽤,利⽤属性 name 可以声明或了解异常的类型,利⽤message 属性可以设置和读取异常的详细信息。
EvalError:Eval 函数执⾏异常。
SyntaxError:语法解析不合理,即语法错误。
RangeError:在数字超出合法范围时抛出,⽐如数组下标越界就会报这种错误。
ReferenceError:在读取不存在的变量时抛出,⽐如没定义变量 a,后⾯却使⽤这个变量 a,就会报这种错。
TypeError:当⼀个值的类型错误时抛出该异常,⽐如传递给函数的参数与预期的不符,就会报这种错误。
URIError:以⼀种错误的⽅式使⽤全局 URI 处理函数⽽产⽣的错误
异常处理
前端捕获异常分为全局捕获和单点捕获。全局捕获代码集中,易于管理;单点捕获作为补充,对某些特
殊情况进⾏捕获,但分散,不利于管理,容易遗漏。在项⽬开发过程中,定义⼀个错误捕获模块,将项⽬所有的异常(全局异常和单点异常)都交给错误模块来统⼀处理,这就需要项⽬约定。
try-catch
try-catch 语句,是 JavaScript 处理异常的⼀种标准⽅式。基本语法如下:
try {
} catch (error) {
// 错误处理
}
try 块中的代码发⽣了错误,就会⽴即退出代码执⾏过程,然后执⾏ catch 块。catch 块会接收到⼀个包含错误信息的对象。⼀般是ssage。
finally
finally 在 try-catch 语句中是可选的,如果 finally ⼦句已经使⽤,则其代码⽆论如何都会执⾏。⽆论 try
syntaxerror是什么错误
或 catch 语句块中包含什么代码——甚⾄ return 语句,都不会阻⽌ finally ⼦句的执⾏。只要代码中包含 finally ⼦句,那么⽆论 try 还是 catch 语句块中的 return 语句都将被忽略。因此,在使⽤ finally ⼦句之前,⼀定要⾮常清楚想让代码怎么样。看下⾯这个函数:
const errorHelper = () => {
try {
return devpoint;
} catch (error) {
return "error";
} finally {
return "不管有⽆错误,我都执⾏了!";
}
};
console.log(errorHelper());  // 函数本⾝是发⽣了异常,但是最终打印的结果为:不管有⽆错误,我都执⾏了!
上⾯的函数代码实际上是有异常的,因为变量 devpoint 并没有定义,不过最终执⾏了 finally ⼦句输出了不管有⽆错误,我都执⾏了!。throw
与 try-catch 语句相配的 throw 操作符,⽤于随时的主动抛出⾃定义错误。
const errorHelper = () => {
try {
return devpoint;
} catch (error) {
return "error";
} finally {
throw new Error("devpoint变量未定义");
}
};
console.log(errorHelper());
异常采集
触发异常有很多原因,为了更好的分析,除了捕获程序的错误信息外,还需要采集执⾏程序的外部环境,对于前端项⽬,外部环境就包括系统(Window、IOS、Android)和系统版本、浏览器(Chrome、IE、⽕狐等)和版本、IP地址、⽤户信息、运⾏的页⾯、⽹络环境、API接⼝数据。针对这些信息就需要设计采集的⽇志结构。
在采集异常⽇志的时候,有个原则需要注意:采集⽇志⾏为不影响⽤户体验及应⽤本⾝的性能。
下⾯是⼀个参考的⽇志结构:
projectId:项⽬信息
eventId:事件ID,⽇志的唯⼀标志
stack:错误stack信息
requestId:开发者定义的异常标志
level:异常级别,可以是 error、info、warn
browser:浏览器信息
device:设备信息
os:操作系统信息
release:应⽤版本信息
url:异常触发页⾯url
user:⽤户信息,可以是iP
createAt:异常产⽣时间
network:⽹络信息
eventKey:触发的键
dataRes:API响应数据
screenWidth:屏幕宽度
screenHeight:屏幕⾼度
message:异常详细信息
异常上报
收集到异常数据如何上报呢?即需要将异常⽇志收集到云端存储,供项⽬开发跟进分析,⼀种⽅式是直接通过API异步上报,在捕获信息⽐较多的情况下,还是会占⽤⽹络请求,影响应⽤本⾝。可以考虑将采集的异常⽇志存储在本地,最佳的选择是IndexedDB,容量⼤,⽀持异步操作,可以⾃定义查询。
IndexedDB 是WEB离线存储的⼀种⽅式,因此存储只是暂时的,还需要设计⼀个同步机制,将本地存
储的⽇志同步到云端服务器上。为了更好的同步,就需要设计暂存区、归档区,新产⽣的⽇志存储在暂存区,已成功同步的⽇志存储在归档区。有了本地存储,同步的过程批量同步。
后端存储,可以考虑使⽤leveldb,在性能⽅⾯,基本可以碾压了mongodb和sqlite。