史上最强egg框架的error处理机制

最强搬运工

异常处理

框架文章阅读

得益于框架支持的异步编程模型,错误完全可以用 try catch 来捕获。在编写应用代码时,所有地方都可以直接用 try catch 来捕获异常。

按照正常代码写法,所有的异常都可以用这个方式进行捕获并处理,但是一定要注意一些特殊的写法可能带来的问题。打一个不太正式的比方,我们的代码全部都在一个异步调用链上,所有的异步操作都通过 await 串接起来了,但是只要有一个地方跳出了异步调用链,异常就捕获不到了。

如果 service.trade.check 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 try catch 统一捕获错误,但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。

框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。

class HomeController extends Controller {
   
  async buy () {
   
    const request = {
   };
    const config = await ctx.service.trade.buy(request);
    // 下单后需要进行一次核对,且不阻塞当前请求
    ctx.runInBackground(async () => {
   
      // 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志
      await ctx.service.trade.check(request);
    });
  }
}

了保证异常可追踪,必须保证所有抛出的异常都是 Error 类型,因为只有 Error 类型才会带上堆栈信息,定位到问题。

框架通过 onerror 插件提供了统一的错误处理机制。对一个请求的所有处理方法(Middleware、Controller、Service)中抛出的任何异常都会被它捕获,并自动根据请求想要获取的类型返回不同类型的错误(基于 Content Negotiation)。

onerror 插件的配置中支持 errorPageUrl 属性,当配置了 errorPageUrl 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。

请求需求的格式 环境 errorPageUrl 是否配置 返回内容
HTML & TEXT local & unittest - onerror 自带的错误页面,展示详细的错误信息
HTML & TEXT 其他 重定向到 errorPageUrl
HTML & TEXT 其他 onerror 自带的没有错误信息的简单错误页(不推荐)
JSON & JSONP local & unittest - JSON 对象或对应的 JSONP 格式响应,带详细的错误信息
JSON & JSONP 其他 - JSON 对象或对应的 JSONP 格式响应,不带详细的错误信息
// config/config.default.js
module.exports = {
   
  onerror: {
   
    // 线上页面发生异常时,重定向到这个页面上
    errorPageUrl: '/50x.html',
  },
};

尽管框架提供了默认的统一异常处理机制,但是应用开发中经常需要对异常时的响应做自定义,特别是在做一些接口开发的时候。框架自带的 onerror 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。

404

框架并不会将服务端返回的 404 状态当做异常来处理,但是框架提供了当响应为 404 且没有返回 body 时的默认响应。

  • 当请求被框架判定为需要 JSON 格式的响应时,会返回一段 JSON:

    {
          "message": "Not Found" }
    
  • 当请求被框架判定为需要 HTML 格式的响应时,会返回一段 HTML:

    <h1>404 Not Found</h1>
    

但是能够支持配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。

// config/config.default.js
module.exports = {
   
  notfound: {
   
    pageUrl: '/404.html',
  },
};

自定义响应404

// app/middleware/notfound_handler.js  中间件
module.exports = () => {
   
  return async function notFoundHandler(ctx, next) {
   
    await next();
    if (ctx.status === 404 && !ctx.body) {
   
      if (ctx.acceptJSON) {
   
        ctx.body = {
    error: 'Not Found' };
      } else {
   
        ctx.body = '<h1>Page Not Found</h1>';
      }
    }
  };
};

配置中间件:

// config/config.default.js
module.exports = {
   
  middleware: [ 'notfoundHandler' ],
};

统一错误处理——中间件的形式

Controller 和 Service 都有可能抛出异常,这也是我们推荐的编码方式,当发现客户端参数传递错误或者调用后端服务异常时,通过抛出异常的方式来进行中断。

  • Controller 中 this.ctx.validate() 进行参数校验,失败抛出异常。
  • Service 中调用 this.ctx.curl() 方法访问 CNode 服务,可能由于网络问题等原因抛出服务端异常。
  • Service 中拿到 CNode 服务端返回的结果后,可能会收到请求调用失败的返回结果,此时也会抛出异常。

app/middleware 目录下新建一个 error_handler.js 的文件来新建一个 middleware

// app/middleware/error_handler.js
module.exports = () => {
   
  return async function errorHandler(ctx, next) {
   
    try {
   
      await next();
    } catch (err) {
   
      // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
      ctx.app.emit('error', err, ctx);

      const status = err.status || 500;
      // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
      const error = status === 500 && ctx.app.config.env === 'prod'
        ? 'Internal Server Error'
        : err.message;

      // 从 error 对象上读出各个属性,设置到响应中
      ctx.body = {
    error };
      if (status === 422) {
   
        ctx.body.detail = err.errors;
      }
      ctx.status = status;
    }
  };
};

加载中间件config/config.default.js

// config/config.default.js
module.exports = {
   
  // 加载 errorHandler 中间件
  middleware: [ 'errorHandler' ],
  // 只对 /api 前缀的 url 路径生效
  errorHandler: {
   
    match: '/api',
  },
};

对error的类型进行判断,返回自定义的message

  • 框架级的错误,一般也不会丢给用户的,egg-onerror 那边兜底统一回复个信息即可,主要还是看日志来修复。
  • 某些在你们业务中并不视为框架级的错误,是可以在 Service 层统一封装抛出的错误类型。
  • 通用的错误可以在 egg-onerror 或者 自定义 Controller 基类里面提供 throwBizErr 这类的方式去处理。

应用自定义

  • onerror 主要处理全局异常,这类基本都是未捕获异常,也就是应用开发者不知道哪里会抛异常,onerror 是用来兜底的。
  • 业务错误一般是应用开发者已知的, 所以都会有对应的处理,常见的就是反回对应的错误文案。这些错误尤其不能出现在错误大盘上,应该使用其他的监控方式,比如 xxx 业务的成功率。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值