异常处理
使用框架提供的统一错误处理器
框架异常处理统一由 @yunflyjs/yunfly-plugin-error
插件进行处理。内置通用处理逻辑。
使用
插件已内置在框架中,开启即可使用。
config/config.default.ts
中启用插件config.error
import {Context,customErrorHandle} from '@yunflyjs/yunfly';
/**
* error handle
*/
config.error = {
enable: true,
// use yunfly default error log.
useYunflyLog: true,
/**
* 错误码
* Type: number | true | Record<Key, Key>
*/
errCode: true,
// 是否开启 HTTP 状态码
enableHttpCode: false,
// 是否返回 rpc 错误信息
useRpcErrorMessage: true,
// 是否返回错误详情
showMessageDetail: true,
/* Customize your error fn. (Optional) */
// customError: async (err: any, ctx: Context) => {}
unhandledRejection: (err: any) => {
console.error('UnhandledRejection error, at time', Date.now(), 'reason:', err);
},
uncaughtException: (err: any) => {
console.error('uncaughtException error, at time', Date.now(), 'reason:', err);
},
// 用于重新组装error信息,处理class-validator错误,并且不影响其他错误
customErrorHandle,
};
字段 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
enable | boolean | true | 是 | 是否开启错误处理 |
errCode | number/true/Record<Key, Key> | 2 | 否 | 错误码 |
useYunflyLog | boolean | true | 否 | 是否开启日志记录 |
enableHttpCode | boolean | false | 否 | 是否开启 HTTP 状态码 |
useRpcErrorMessage | boolean | true | 否 | 是否返回 rpc 错误信息 |
showMessageDetail | boolean | false | 否 | 是否返回错误详情 |
customError | (err: any, ctx: Koa.Context) => any | 否 | 自定义错误,若定义,则不会执行yunfly-plugin-error 中间件后续逻辑 | |
customErrorHandle | (err: any, ctx: Koa.Context) => any | 否 | 可用于重新组装错误,并不影响yunfly-plugin-error 中间件后续逻辑的执行 | |
unhandledRejection | (err: any, ctx: Koa.Context) => any | 否 | 自定义 Promise 错误 | |
uncaughtException | (err: any, ctx: Koa.Context) => any | 否 | 自定义未能捕获的异常 |
参数说明
errorCode
不同的错误类,有不同的错误码,但为了满足不同用户场景以及向后兼容,为此我们做了如下的处理:
1、当 errorCode 为数字时:那么错误码一直就是这个数字
// 配置
config.error = {
enable: true,
useYunflyLog: true,
errorCode: 2, // 则返回 code 一直为 2
}
// 无论什么错误,code 都为 2
ctx.body = {
code: 2,
msg: string,
}
2、当 errorCode 为带 * 对象时(改造中): { "*": 2, 401: 401 }
代表 401 错误码,返回 401,其他返回 2
// 配置
config.error = {
enable: true,
useYunflyLog: true,
errorCode: {
401: 401, // 遇到 401 错误,code 码值为 401
'*': 2, // 其他情况为 2
},
}
// 抛错
import { UnauthorizedError } from '@yunflyjs/yunfly'
throw new UnauthorizedError('jwt token验证失败!')
// 返回结果
ctx.body = {
code: 401,
msg: 'jwt token验证失败!',
}
3、当 errorCode 为不带 * 的对象时(改造完成后的特殊处理,常用于 rpc 错误码映射):{ 2000210: 401}
代表如果遇到错误码为 2000210,则返回 401,其他情况下返回原错误码。
// 配置
config.error = {
enable: true,
useYunflyLog: true,
errorCode: {
2000210: 401, // 遇到 2000210 错误码时,返回 401
},
}
// 抛错
import { RpcError } from '@yunflyjs/yunfly'
const { error, response } = await this.userService.getUserInfo({request})
if (error) {
// 假定这个 error 的状态码是 2000210,表示用户未登录,我们映射到前端就是 401
throw new RpcError(error)
}
4、当 errorCode 为 true 时,则代表所有的错误码为原错误码,不做任何映射,如果是 throw 的普通 Error,默认值依然是 2
。
// 配置
config.error = {
enable: true,
useYunflyLog: true,
errorCode: true, // 一直采用 Error 类的 code,如果是 Error 类中没有 code,则返回 2
}
import { BadRequestError } from '@yunflyjs/yunfly'
throw new BadRequestError('用户名不为空')
{
code: 400,
msg: "用户名不为空"
}
customError
我们可以通过实现 customError
方法,从而实现错误的自定义处理
config.error = {
enable: true,
useYunflyLog: true,
customError: async (err: any, ctx: Context) => {
const message =
typeof err === 'string' ? err : err.details || err.message || ''
ctx.body = {
code: 2,
msg: message || '服务异常,请稍后重试!',
}
},
}
customError
暴露err
和ctx
两个参数,可根据自己的需求进行自定义处理,最后通过ctx.body = xxx
进行返回结果处理。
useYunflyLog
- 框架默认错误输出字段描述
当 useYunflyLog
为 true
时会自动将错误信息记录到阿里云日志系统中,记录的内容如下:
方法名 | 说明 |
---|---|
url | 请求 URL |
method | 请求类型 |
request | 所有请求参数(兼容 get、post) |
headers | http 请求头信息 |
trace-id | 链路id |
error | error 错误详细信息 |
当参数
useYunflyLog
为true
时, 无论是否自定义错误函数, 框架都会打印错误日志。
customErrorHandle
从新定义 error
错误信息,核心处理 class-validator
错误信息
import { customErrorHandle } from '@yunflyjs/yunfly'
config.error = {
// 用于重新组装error信息,处理class-validator错误,并且不影响其他错误
customErrorHandle,
}
enableHttpCode
-
是否开启 HTTP Code
-
false:不开启,默认值, HTTP code 一直是 200
-
true:开启,如果 Error 类 httpCode 属性有值,则用 Error 类的 httpCode,没有值,则默认值是
500
。
useRpcErrorMessage
-
是否使用 rpc 错误信息作为 msg 的值
-
true:表示使用 rpc 错误信息作为响应结果的 msg 结果
{
code: 200010, // go 返回的错误
msg: "Error: 2 UNKNOWN: 支付订单不存在"
}
- false:表示使用统一的 “服务器错误,请重试” 作为 msg 结果
{
code: 200010, // go 返回的错误
msg: "服务器错误,请重试"
}
showMessageDetail
- 是否返回详情信息
针对服务器运行时错误、rpc 错误或者传递了 messageDetail
属性的错误,是否返回 mesageDetail
信息。
- true
{
code: 500,
msg: "服务器错误,请重试",
msg_detail: "Cannot read property 'doSomething' of undefined" // 返回
}
- false
{
code: 500,
msg: "服务器错误,请重试"
}
unhandledRejection
- 自定义 Promise 错误
config.error = {
enable: true,
useYunflyLog: true,
unhandledRejection: (err: any) => {
console.error(
'UnhandledRejection error, at time',
Date.now(),
'reason:',
err
)
}
}
uncaughtException
- 自定义未能捕获的错误
config.error = {
enable: true,
useYunflyLog: true,
uncaughtException: (err: any) => {
console.error(
'uncaughtException error, at time',
Date.now(),
'reason:',
err
)
},
}
抛错类型
当程序出现错误时, 抛错有多种方式。
断言
import { strict as assert } from 'assert';
@Get('/jest')
test(
@BodyParam('name') name: string,
): string {
assert.equal(typeof(name) === 'string', true , "name 参数必须为字符串类型!");
return name || 'success';
}
// 返回结果
ctx.body = {
code: 2,
msg: 'name 参数必须为字符串类型!',
}
throw 抛错
import { strict as assert } from 'assert';
@Get('/jest')
test(
@BodyParam('name') name: string,
): string {
if (typeof(name) !== 'string') {
throw "name 参数必须为字符串类型!"
}
return name || 'success';
}
// 返回结果
ctx.body = {
code: 2,
msg: 'name 参数必须为字符串类型!',
}
throw Error 错误类
import { strict as assert } from 'assert';
@Get('/jest')
test(
@BodyParam('name') name: string,
): string {
if (typeof(name) !== 'string') {
throw new Error ("name 参数必须为字符串类型!");
}
return name || 'success';
}
// 返回结果
ctx.body = {
code: 2,
msg: 'name 参数必须为字符串类型!',
}
throw YunkeError 错误类 (推荐)
import { strict as assert } from 'assert';
import { BadRequestError } from '@yunflyjs/yunfly'
@Get('/jest')
test(
@BodyParam('name') name: string,
): string {
if (typeof(name) !== 'string') {
throw new BadRequestError ("name 参数必须为字符串类型!");
}
return name || 'success';
}
// 返回结果
ctx.body = {
code: 400,
msg: 'name 参数必须为字符串类型!',
}
- 更多错误类请参考:错误类使用
Service 错误处理
global middleware
框架所有方法错误信息默认由全局 ErrorMiddleware
中间件进行错误处理,一旦有任何报错,都会返回error信息
import { Service } from "typedi";
//
@Service()
export default class UserService {
// 用户鉴权
async auth(userId: number, ctx: any): Promise<any> {
const response = {}
// 以下代码会报错,会使用全局的ErrorMiddleware进行错误处理
response.data.name = 'xiaowang'
return response
}
}
try catch
import { Service } from "typedi";
//
@Service()
export default class UserService {
// 用户鉴权
async auth(userId: number, ctx: any): Promise<any> {
// 使用 try catch 手动处理error信息
// 使用场景: 聚合多个接口时,此接口的报错不影响整个请求的成功状态
try {
const response = {}
response.data.name = 'xiaowang'
return response
} catch(err) {
return err
}
}
}
Decorator Catch
- 自定义
Catch
装饰器处理错误信息
export function Catch(option?: ExtSdkOption): Function {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const _fn = descriptor.value;
descriptor.value = async function (...args: any[]): Promise<any> {
try {
return await _fn.apply(this, args);
} catch(err: any) {
// 自定义错误处理逻辑
const error = new Error();
error.message = '自定义错误信息';
error.code = 2;
throw error;
}
}
return descriptor;
};
}
import { Service } from "typedi";
import { Catch } from "../utils/decorator"
//
@Service()
export default class UserService {
// 用户鉴权
@Catch()
async auth(userId: number, ctx: any): Promise<any> {
// 使用 Catch 装饰器处理error信息
// 使用场景: 聚合多个接口时,此接口的报错不影响整个请求的成功状态
const response = {}
return response
}
}
ServiceV2
- 使用自定义
ServiceV2
接收错误信息并手动处理
import { Service } from "typedi";
import { Catch } from "../utils/decorator"
// services
import { usrUserServiceV2 } from "../grpc-code-gen/2c/marvel-user/user/UsrUserService";
//
export interface RpcServiceResponse<T = any> {
'error'?: any;
'error_derails'?: string;
'response': T;
'metadata': any;
}
//
@Service()
export default class UserService {
// 用户鉴权
async auth(request: AnyOptions, ctx: any): Promise<any> {
// 使用 ServiceV2 处理error信息
// 使用场景: 聚合多个接口时,此方式可以自定义错误抛出方式
const { error, response }: RpcServiceResponse = await usrUserServiceV2.GetUserInfot({ request });
if (error) {
throw error;
}
return response
}
}
如何输出错误日志
yunfly
框架代理了console
方法,我们只需要打印错误信息,即可记录到日志系统中。
console.error('记录错误日志!');
console.error('开发环境请在 log/error.log 中查看错误信息!');
自定义错误处理器
- 创建
src/middleware/ErrorMiddleware.ts
文件,定制自己的错误处理逻辑。
import { KoaMiddlewareInterface, Middleware, Context, logger } from '@yunflyjs/yunfly';
@Middleware({ type: 'before', priority: 20 })
export default class YunflyPluginErrorMIddleware implements KoaMiddlewareInterface {
async use(ctx: Context, next: (err?: any) => Promise<any>): Promise<any> {
try {
await next();
} catch (err: any) {
// 定制自己的错误处理器
logger.error({ msg: 'Request error', url: ctx.req.url, error: err });
// 返回错误信息
const body: Record<string, any> = {
code: err.code || 400,
msg: err.message || '服务器异常',
};
ctx.body = body;
}
}
}
- 关闭内置 error 插件
config.error = {
enable: false,
}