前言
这篇文章想要说明的是
- JavaScript 异常的概念
- JavaScript 异常的处理
- JavaScript 异常的收集
- JavaScript 异常的分析(后面再更新)
前端工程师都知道,JavaScript 拥有基本的异常处理方法,但是一般前端工程师都不会处理异常。其实情有可原,对于普通网站来说,异常是非常可控的,基本可以刷新页面就解决问题,运行环境+代码运行也很少带来异常。但是对于业务富集的webApp/大型组件来说,没有异常处理会是异常灾难。无论在用户使用场景或者在开发/调试 作为出发点,异常处理都是非常必要的
异常处理非常重要,它可以提高程序健壮性,对于其他软件开发来说,健壮性是非常重要的一项指标。
这里会引用一篇实用性很强的文章,在错误日志捕抓/收集提供了一些实用的方法,这里提供文章传送门
JavaScript 异常的概念
异常的出现
当你的JavaScript语法,错误的调用方式,变量没有定义,等等这些都会出现错误,而这种异常JavaScript会抛给开发者。我们能在控制到看到异常的具体信息,包括异常类型,异常所在的JS文件,异常所在的行数,异常的堆栈信息,这些异常信息都是让我们能更迅速的找到异常并且解决。
异常的载体
异常通过Error对象表达出来,对象的属性包括:
来源Mozilla官方文档
Microsoft
Error.prototype.description
Error.prototype.number
Mozilla
Error.prototype.fileName // 出现异常的文件路径
Error.prototype.lineNumber //发生异常的js文件行数
Error.prototype.columnNumber //发生异常的js文件列数
Error.prototype.stack // 异常的堆栈信息
Error对象的子类
EvalError
InternalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
我们通过 throw 关键字能向浏览器抛出一个异常对象
throw new Error('Whoops!'); //Error
其他z
这里不对Error 属性以及原理讨论,有兴趣的同学可以去Mozilla,或者W3官方查阅相关文档。
JavaScript 异常的处理
编程语言都会提供处理异常的方法,就是JavaScript就是 try catch 语句,通过try方法捕抓异常,捕抓到异常后会退出try 代码块(即{//code}),然后执行catch语句,catch有一个Error对象的实参,在catch代码块能引用这个Error对象。
捕抓错误的语句:
try{
//your code
}catch(e){
//while raise a error ,will exc this code
}
catch(e){
//while raise a error,and pre catch has beed run ,will exc this code
}
catch(e){
//while raise a error,and pre catch has beed run ,will exc this code
}final{
//whether raise a error, this code will be run
}
try语句允许我们定义在执行时进行错误测试的代码块。
catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。
finally 语句在 try 和 catch 之后无论有无异常都会执行。
注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。
JavaScript 异常的收集
对于后端来说错误日志收集是个老生常谈的话题,在前端还是相对听的少,比较集成服务器的构建,日志分析等等并不是前端必需掌握的技能。
捕抓错误
对于web前端来说JavaScript的异常还是相对好捕抓的,异常出现以后,会触发错误事件传递给window对象, 可以用 window.onerror = function(){}来捕抓页面上每一个错误,所以捕抓错误很简单。
错误信息
我们要把我们需要的错误信息,post去我们的日志记录服务器那里,要做的:
- 整理需要记录的信息(包括,error属性,userAgent等等)
- error兼容性处理,不同浏览器error对象的属性会有差异,要做兼容处理
- 序列化消息,日志会以字符串的形式上传,属性需要做序列化处理
- 属性丢失,我们会想要收集些比较有用的信息包括:错误类型(name)、错误消息(message)、脚本文件地址(script)、行号(line)、列号(column)、堆栈跟踪(stack)。如果一个异常是通过 try-catch 捕获到的,这些信息都在 Error 对象上(主流浏览器都支持)。但如果是通过 window.onerror 捕获到的,我们都知道这个事件函数只有 3 个参数,所以这 3 个参数以外的信息就丢失了。
注意不要耍小聪明使用 window.addEventListener 或 window.attachEvent 的形式去监听 window.onerror。很多浏览器只实现了 window.onerror,或者是只有 window.onerror 的实现是标准的。考虑到标准草案定义的也是 window.onerror,我们使用 window.onerror 就好了。
实践与分析(更新2016-12-12)
基于大神的实现经验,这段时间也搭起来自己的服务器来收集日志总结一下这段时间的遇到的问题,以及记录自己异常分析思路。
前提:
首先说明项目技术栈。项目技术栈毕竟老旧,jquery+require,项目看重seo,当时也没有能力搭起node-server 做服务端渲染,所以项目还是以传统网站的实现方式实现。
遇到的问题 (update on 2017年1月9日,旧内容会用斜体注释):
js文件跨域
把资源放到cdn是一个非常常见的 优化方案,但是当遇到require问题就来了。一般跨域在script里面添加 crossorigin 属性就可以了,但是由于资源加载管理,放在require里面,所以我的解决方案是修改require 源码,在创建script标签的时候添加 crossorigin属性。
修改的源码
req.load = function (context, moduleName, url) {
var config = (context && context.config) || {},
node;
if (isBrowser) {
//In the browser so use a script tag
node = req.createNode(config, moduleName, url);
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
node.crossOrigin="anonymous" //添加跨域头
但是问题并不能完全解决,ie浏览器上script标签没有实现 crossOrigin 属性,ie上的异常还是没办法收录。
由于reuqire 完全掌握js文件加载,所以我们也没有办法使用大神解决方案来解决我的问题。陷入绝望ing
update on 2017年1月9日
关于 script error 的解决方案
由于看到了大神的一些经验总结,在文章当中找到一些相关的资料,这里记录 script error 原因&解决方案;
出现 script error 的场景和条件:
- 通过window.onerror 注册监听脚本错误事件
- 浏览器:Firefox,Chrome,Safari,IE7+(*)
- 页面内使用了script 标签引入,非同源的资源,且发生了脚本错误
本质上是出于安全考虑。因为 script 标签引入文件内容的时候是忽略文件本身的MIME声明,且是允许跨域请求的。这里如果不屏蔽掉跨域情况下的错误信息,很可能会给黑客提供一个攻击通道。
webkit 处理源码
//http://trac.webkit.org/browser/trunk/Source/WebCore/dom/ScriptExecutionContext.cpp#L347
bool ScriptExecutionContext::sanitizeScriptError(
String& errorMessage,
int& lineNumber,
int& columnNumber,
String& sourceURL,
CachedScript* cachedScript
)
{
URL targetURL = completeURL(sourceURL);
if (securityOrigin()->canRequest(targetURL) ||
(cachedScript &&
cachedScript->passesAccessControlCheck(*securityOrigin())
)
)
怎么解决 Script error ?
1. 添加同源策略
在脚本文件的 HTTP response header 中设置 CORS
比如: Access-Control-Allow-Origin: http://yuncaijing.com
在页面的 script 标签中设置 crossorigin 属性
比如:<script src="http://7.url.cn/index.js" crossorigin></script>
CORS header 和 crossorigin 取值问题
- anonymous(默认)
CORS 不等于 origin, 不能带 cookie - use-credentials
Access-Control-Allow-Credentials: true
CORS 不能设置为 *,能带 cookie
CORS 不等于 origin,js 直接不加载
关于CORS header 和 crossorigin的资料
2. 使用try - catch (通用不丢失错误信息方法)
- 每个方法都包裹上 try catch
- 对主要入口方法进行try catch
!!!!只需要对异步模块和业务主入口添加 try catch
- setTimeout 和 setInterval
- 事件绑定
- ajax callback
- define 和 require
- 业务主入口
补充问题
通过 XHR 获取脚本资源, 通过 script 标签 innerText 插入到页面,到导致会丢失错误的行号列号和文件名等信息
资源合并压缩
资源压缩合并也是非常常见的优化手段。压缩合并后的js文件完全是没法阅读的,想找到异常的位置,需要根据堆栈信息的行列号,到压缩合并后的js文件中 匹配,这个debug过程相对不智能,
//解决方案 以后找到再更新
收集的信息
我的数据包字段是这样的
{
host:'',//域
path:'',//url 路径
file:'',//异常文件
stack:'',//异常堆栈信息
ua:'',//经过前端处理的ua信息 (xx系统 xx浏览器 xxx版本)
userAgent:'',//浏览器ua字段
}
这里收集起来最有用的就是ua,和stack。这两个是反映真实用户的终端设备,和异常的信息的,这两个信息对于找到bug的原因有十分大的帮助。
这里建议在浏览器中判断ua的类型,在服务器中光靠userAgent字段是没办法真实的判断浏览器环境的
分析
这里简单说一下分析思路。
日志收集过来,所以首先第一步明确我们想从这堆信息中获得什么信息。
1. 系统发生异常的概率
2. 按照某个周期排列某种异常(可以是总数 , 可以是某种异常,可以是异常发生概率),观察趋势。
3. 某个html文件(某个url,或者某种url)发生错误的占比。
4. 某个js文件发生异常占比
5. 某个错误信息出现的次数
6. 某个异常,会有多少种异常
以上几点都会以图表形式展示,我分析的目标主要侧重趋势,图表能直观反映某个状态的严重状况,反映编码质量/测试质量上升还是下降。
参考资料:
Mozilla官方文档
大神实践总结