一、背景
Sentry 可以有效帮助我们发现项目中存在的问题。经过一段时间的使用和了解,我们对其中的错误进行了汇总。
我们发现有大量的错误是由于 HTTP 请求 400 或者 401 导致。
二、错误分析
现有的 MSP 请求封装了统一的 request 请求,对 request.repsonse
进行了 interceptor
操作。即:进行了 response
的统一处理,由自己处理完后再返回给业务层。
之前我们在 interceptors.response
中对部分业务码进行了统一处理,如:401 未授权跳转到登录页。
之前为了忽略部分状态码,直接进行了 return
操作,此操作后,业务层会进入 Promise.then
环节。由于在 http status 400 或 401 情况下,API 返回的数据格式异常,所以导致了 Cannot read property of null
的错误。
request-helper.js
的 return 操作
1 | if (statusCode === 401) { // 未授权的登录,这种情况需要重定向到登录页面 |
user.js
读取
1 | refreshToken({ commit }) { |
三、处理分析
关于部分请求的错误,如 401、403我们需要进行放行。理由如下
- 401,用户没有授权,属于正常业务逻辑,不需要上报
- 400,参数认证失败,属于正常的业务逻辑,不需要上报
- 500,待定,此类问题一般由于内部错误导致(还有发版时会遇到),可以先正常上报。
所以我们看到,需要对 reponse 进行统一的兜底处理。但是我们发起方是在业务代码里(vue
文件),所以是否存在 业务层的统一处理?
答案是:存在的。借助 window.onunhandledrejection
统一处理异常 main.js
1 | window.onunhandledrejection = function(error) { |
业务 user.js
1 | return new Promise((resolve, reject) => { |
终端输出
猜想:sentry 或者 vue,会不会使用这种方式统一捕捉错误。如果大家都用了,这个错误就不能有效传导了。所以我们需要保留其他已注册函数的处理句柄。
1 | const _oldHandler = window.onunhandledrejection |
通过保留上次处理函数句柄的方式,可以有效传导
四、实现
综上,我们设定一个全局的错误处理函数来进行错误捕捉,而不用逐个去修改源码。处理方式如下
- 1)request 层正常 reject 所有错误(即:正常向上 throw error)
- 2)在 main.js 中注册全局的错误处理函数
4.1 正常 reject error
之前由于其他地方零零散散的会上报错误,部分被注释了,现在统一打开,正常在 Promoise.reject
4.2 注册全局处理函数
main.js
注册全局处理函数
1 | import { globalErrorHandler } from './error-handler' |
error-handler.js
处理错误
1 | // 定义需要忽略上报的 http status code |
这里需要注意的是,需要先拦截到 sentry 的钩子,然后进行对应的情况处理。
当满足我们忽略条件时,不再进行上报。其他情况,正常上报
五、总结
- 不要干扰正常的异常抛出流程,该抛出异常时,大胆抛出
使用统一处理函数
window.onunhandledrejection
进行统一错误捕获。基本上目前主流的语言(或框架)都会提供统一的错误处理机制,所以首先想到
的一定是“统一”处理,而不是每个业务代码都去改动。- PHP 错误全局捕获
set_exception_handler
set_error_handler
- https://www.php.net/manual/en/function.set-error-handler.php
- https://www.php.net/manual/en/function.set-exception-handler.php
- PHP 错误全局捕获
注意使用
window.onunhandledrejection
时,不要干扰已经注册的处理函数,所以可以先“暂存”,之后再“恢复”。这个和 PHP 内核拓展开发时,hook
内置的函数有异曲同工之妙。- PHP 内核示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 通过在 Module Init 环节拦截函数入口,进而 Hook,进行一系列自定义处理
PHP_MINIT_FUNCTION(skywalking)
{
ZEND_INIT_MODULE_GLOBALS(skywalking, php_skywalking_init_globals, NULL);
//data_register_hashtable();
REGISTER_INI_ENTRIES();
/* If you have INI entries, uncomment these lines
*/
if (SKYWALKING_G(enable))
{
if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0)
{
return SUCCESS;
}
// 用户自定义函数执行器(php脚本定义的类、函数)
ori_execute_ex = zend_execute_ex;
zend_execute_ex = sky_execute_ex;
// 内部函数执行器(c语言定义的类、函数)
ori_execute_internal = zend_execute_internal;
zend_execute_internal = sky_execute_internal;
- PHP 内核示例