如何在KoaJS中处理错误?
KoaJS是洋葱模型,请求进入和返回时,会“经过”2次中间件。所以可以直接在第一个中间件中,通过try-catch捕获服务的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async function errHandlerMiddleware(ctx, next) { logger.log({ logType: 'accessLog', reqPath: ctx.request.path, reqId: ctx.request.reqId })
try { await next() } catch (err) { logger.error({ logType: 'errHandle', reqId: ctx.request.reqId, errStack: inspect(err.stack), errMsg: err.message }) ctx.res.status = 500 ctx.res.body = "server error" } }
|
配合process.on,可以监听未捕获的unhandledRejection
、uncaughtException
如何在Express中处理错误?
Experss不是洋葱模型,而是中间件进行「串行处理」,请求返回时,不会再“经过”前面的中间件。所以,要在中间件中,通过事件监听,来响应服务中的报错,并且进行打印:
1 2 3 4 5 6 7 8
| function errHandlerMiddleware(req, res, next) { res.on('finish', () => { }) res.on('error', (err) => { }) }
|
在这个点上,express 不如 koa 优雅。
配合process.on,可以监听未捕获的unhandledRejection
、uncaughtException
如何在NestJS中处理错误?
分类讨论错误类型
由于Nest基于Express 2次开发,并且加了Ioc,因此处理起来比较棘手。
按照错误类型,可以按照以下方法来处理:
- 未捕获的
unhandledRejection
、uncaughtException
,比如异步处理报错、第三方库错误:通过process.on监听
- NestJS内置错误
HttpException
:通过全局Filter来捕获
- 服务定义的标准错误,继承自
HttpException
:通过全局Filter来捕获
- 服务未定义的错误:通过全局Filter来捕获
- 第三方库的错误:通过全局Filter来捕获
- HTTP 库的错误
- 非 HTTP 错误
实现标准错误类
项目中不允许直接在代码中,直接throw new Errro(xxxx)
抛出错误。此类错误会被Filter识别为sysErr
系统错误。
所有的错误都要通过标准的方法makeErr
进行抛出。
抛出的错误继承自 HttpException
,新增了错误码code
属性。
整体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import { HttpException, HttpStatus } from '@nestjs/common'; import { isString } from 'lodash';
interface IMakeErrOpts { code: string; message: string; statusCode?: number; stack?: string; }
export class BaseError extends HttpException {
public readonly code: string;
constructor(code, message, stack, statusCode) { super(message, statusCode); if (isString(stack)) { this.stack = stack; } this.code = code; } }
export function makeErr(opts: IMakeErrOpts) { const { code, message, stack, statusCode = HttpStatus.INTERNAL_SERVER_ERROR, } = opts; return new BaseError(code, message, stack, statusCode); }
|
实现错误捕获Filter
从上面看到,2-5都通过Filter来捕获。因此,要在Filter中区分处理不同类型报错。
- 内置错误和继承自内置错误的标准错误:返回业务的错误码,打印错误信息
- 未定义错误:开发阶段未发现、未处理成标准错误的异常。返回系统错误码,红体打印系统错误。
- 第三方库的错误:
- HTTP 库错误:error上一般都有status,识别status即可。如果status是4xx,那么处理和第一类一样;否则,和第二类一样
- 非 HTTP 错误:看是否提供事件监听或者try-catch捕获,不然就是和第二类一样,属于未捕获错误。
整体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Catch() export class AllExceptionFilter implements ExceptionFilter { catch(exception: any, host: ArgumentsHost) { const ctx = host.switchToHttp(); const res: ICtxResponse = ctx.getResponse(); const req: ICtxRequest = ctx.getRequest(); const isSysErr = !( exception instanceof HttpException || (exception.status >= 400 && exception.status < 500) );
const logType = isSysErr ? 'sysErr' : 'responseErr'; const status: number = isSysErr ? HttpStatus.INTERNAL_SERVER_ERROR : exception.status || HttpStatus.BAD_REQUEST; const errCode: string = isSysErr ? SERVER_ERR_CODE : exception.code || BAD_REQUEST_CODE; const errMsg: string = exception?.response?.message ? exception.message + '. ' + exception?.response?.message : exception.message;
baseLog.print({ logLevel: isSysErr ? 'error' : 'warn', reqId: req.reqId, logType, logTime: Date.now(), errStatus: status, errMsg, errStack: exception.stack, errCode, });
req.accessErr = { status, code: errCode, message: errMsg, };
res.set('X-Content-Type-Options', 'nosniff').status(200).json({ reqId: req.reqId, code: errCode, msg: errMsg, }); } }
|