记录下angular项目中,借助sentry完成前端代码错误日志监控
前言
sentry是一个不错的代码异常日志的统计工具,可以在不同的平台上运行。传送门
1
通过Raven,可以添加额外的日志信息1
2
3
4
5try {
doSomething();
} catch(e) {
Raven.captureException(e)
}
2
Raven可以定义全局的异常捕获1
2
3
4
5
6
7
8
Raven.config(, {}).install();
foo.bar(); // this error will be reported to Sentry
Sentry 可以帮助我们,去发现一些无法通过单元测试和e2e去发现的异常,通常这些异常也很难通过用户去发现。
下面说明如何利用sentry,在angular项目中,尽可能的捕获到已经客户端发生的异常。
3
angular有一个全局的$exceptionHandler的factory,能够捕获到在controller、services等内发生的异常。$exceptionHandler默认的实现方式,只是简单的打印出来,并重新抛出该异常。下面复写改该方法,并转发异常信息到sentry1
2
3
4
5
6
7
8angular.module('ErrorCatcher', [])
.factory('$exceptionHandler', function (){
return function (exception, cause){
console.error(exception.stack);
Raven.captureException(exception);
// do not rethrow the exception - breaks the digest cycle
};
});
使你的顶层module依赖该ErrorCatcher模块,这样代码中的异常就会被sentry收集到。
注意到第二个参数cause,通常的值是undefined。不过当词法解析的异常发生时,该参数将会有值。举个例子,在自定义指令中,link函数中尝试使用非法元素的初始标签作为第二参数。1
2
3
4
5
6
7function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn){
try {
linkFn(scope, $element, attrs, controllers, transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
通过浏览器console,可以尝试在angular的事件循环中抛出错误1
2
3
4
5
6angular.element(document.body)
.injector()
.get('$rootScope')
.$apply(function (){
throw new Error('from angular');
});
除了document.body,还可以使用angular.element($0)来替代。
4
通过设置$http的拦截器,错误的服务端响应也可以被sentry收集到。在定义 ErrorCatcher模块时,添加该拦截器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17angular.module('ErrorCatcher', [])
.factory('errorHttpInterceptor', ['$q', function ($q){
return {
responseError: function responseError(rejection){
Raven.captureException(new Error('HTTP response error'), {
extra: {
config: rejection.config,
status: rejection.status
}
});
return $q.reject(rejection);
}
};
}])
.config(['$httpProvider', function($httpProvider){
$httpProvider.interceptors.push('errorHttpInterceptor');
}]);
抛出异常的正确方式
通过Error实例对象抛出异常,不能通过抛出字符串或其他对象。Error实例的抛出,才会打印出堆栈信息1
2throw new Error('broken') // good
throw 'broken' // bad
抛出错误会导致代码执行的中断,如果错误不是必要的,可以异步抛出1
2
3
4
5if (!some_condition) {
setTimeout(function (){
throw new Error('some_condition failed!');
}, 0);
}
可以封装成工具方法1
2
3
4
5
6
7
8
9function asyncThrow(err){
if (!(err instanceof Error)) {
throw new Error('please throw only instance of Error, not ' + err);
}
setTimeout(function (){ throw err; }, 0);
}
if (!some_condition) {
asyncThrow(new Error('some_condition failed!'));
}