记录下angular项目中,借助sentry完成前端代码错误日志监控

前言

sentry是一个不错的代码异常日志的统计工具,可以在不同的平台上运行。传送门

1

通过Raven,可以添加额外的日志信息

1
2
3
4
5
try {
doSomething();
} catch(e) {
Raven.captureException(e)
}

2

Raven可以定义全局的异常捕获

1
2
3
4
5
6
7
8
<script src="//cdn.ravenjs.com/1.1.2/jquery,native/raven.min.js"></script>
<script>
// DSN is your projects API key
Raven.config(<DSN>, {}).install();
</script>
<script>
foo.bar(); // this error will be reported to Sentry
</script>

Sentry 可以帮助我们,去发现一些无法通过单元测试和e2e去发现的异常,通常这些异常也很难通过用户去发现。

下面说明如何利用sentry,在angular项目中,尽可能的捕获到已经客户端发生的异常。

3

angular有一个全局的$exceptionHandlerfactory,能够捕获到在controllerservices等内发生的异常。$exceptionHandler默认的实现方式,只是简单的打印出来,并重新抛出该异常。下面复写改该方法,并转发异常信息到sentry

1
2
3
4
5
6
7
8
angular.module('ErrorCatcher', [])
.factory('$exceptionHandler', function () {
return function errorCatcherHandler(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
7
function 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
6
angular.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
17
angular.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
2
throw new Error('broken')  // good
throw 'broken' // bad

抛出错误会导致代码执行的中断,如果错误不是必要的,可以异步抛出

1
2
3
4
5
if (!some_condition) {
setTimeout(function () {
throw new Error('some_condition failed!');
}, 0);
}

可以封装成工具方法

1
2
3
4
5
6
7
8
9
function 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!'));
}

参考