美文网首页
egg框架的异常返回页面的生成

egg框架的异常返回页面的生成

作者: Yeruyi_Roy | 来源:发表于2019-07-05 17:07 被阅读0次

说来惭愧,业务到现在都是单点服务,且无上报,对于测试发现的问题只有同步库查日志(测试环境日志都很难找),萌生了异常上报的想法,说到异常,不由地说egg的错误页面,多么精致,今天就来分析一下页面的产生

egg-onerror的处理

egg框架在egg包中调用了egg-onerror包,并且开启了这个plugin。进入看一下egg-onerror/app.js

const onerror = require('koa-onerror');
...
module.exports = app => {
  // 1
  const config = app.config.onerror;
  const viewTemplate = fs.readFileSync(config.templatePath, 'utf8');
  
  // 2
  app.on('error', (err, ctx) => {
    ctx = ctx || app.createAnonymousContext();
    if (config.appErrorFilter && !config.appErrorFilter(err, ctx))
      return;

    const status = detectStatus(err);
    // 5xx
    if (status >= 500) {
      try {
        ctx.logger.error(err);
      } catch (ex) {
        app.logger.error(err);
        app.logger.error(ex);
      }
      return;
    }

    // 4xx
    try {
      ctx.logger.warn(err);
    } catch (ex) {
      app.logger.warn(err);
      app.logger.error(ex);
    }
  }
  
  // 3
  const errorOptions = {
    ...
  }
  // support customize error response
  [ 'all', 'html', 'json', 'text', 'js' ].forEach(type => {
    if (config[type]) errorOptions[type] = config[type];
  });
  
  // 4
  onerror(app, errorOptions);
}

可以看到这货接下来调用的是koa-onerror, 我们先停留在这层做个简单分析。这层到底做了什么?简要概括如下

  1. 读取预配置,默认读取./lib/onerror_page.mustache的模板,并且通过config可以自定义配置信息
  2. 监听对error事件监听,并且执行动作。
  3. 构建errorOptions对象,完成对 accepts、html、json、js的处理handler方法。并且注入到config中,参考注释方法
  4. 传递调用koa-onerror方法

关于app的onerror事件,是对状态码判断并输送至指定日志对象上,输出日志作用。这里是对处理的一个截断,我们继续往下看koa-onerror

koa-onerror的处理

这里的处理全部在app.context.onerror的对象方法赋值上. 那么问题来了,app的app.on('error',func)app.context.onerror有what子区别。


咳咳咳,先暂停一下,小的有事禀报...

插曲1:分析 app.on('error',func) 和 app.context.onerror 的浅析

(ps: 这个时候我来没看过koa相关的东西...)

期初拿到这个问题,我脑子里面蹦出来的几点念想:

  1. 都知道,egg是个框架,egg框架里面对于app的引用自然很多,我该怎么找。
  2. (猜测1)指不定onerror就是某个文件的某个地方是app.on('xxxx',func)func对象呢?(猜测2)event事件会不会是个截断,和上文类似?

接着往下看,刚才koa-onerror的代码某处有一点:koa-onerror/index.js

app.context.onerror = function(err) {
  ... 
  this.app.emit('error', err, this);
  ...
}

就是说一个context.onerror是个方法,另外这行代码有点刺眼。是方法总有地方会调用吧,其二,为什么会在一个onerror单词emit执行了error的event事件,且将自己的err参数发过去了,明白这是一个主动触发。context哪来的,egg.ctx给的。egg哪来的?koa...(说npm下载下来的准备开打了...)
koa的里面有application.js、context.js、requset\response.js, ...(中间省略几行我傻不拉几去找eggApplication的error处理的过程), 大家都是到koa是怎么调用的, KOA以下代码来自官文,不解释了。

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

app.listen对吧,OK, 我们看下KOA的koa/application.js

listen(...args) {
  debug('listen');
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

返回调用在this.callback()上,继续追进去

callback() {
  ...
  if (!this.listenerCount('error')) this.on('error', this.onerror);
  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res);
    return this.handleRequest(ctx, fn);
  };
  return handleRequest;
}

handleRequest(ctx, fnMiddleware) {
  const res = ctx.res;
  res.statusCode = 404;
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  onFinished(res, onerror);
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

event方法listenerCount就不解释了,返回监听这个事件的数量,Application是继承Emitter的,因此这里判断,如果没有error事件监听,就丢给了this.onerror,也就是application.onerror(注意这里对象是app)。这里还不是context的,很容易理解啊,在其位谋其政,app的onerror是处理平台级的错误信息。继续看handleRequest是不是就找到了context.onerror了,当然走到这步了就继续看看koa.context对onerror的实现

onerror(err) {
    // don't do anything if there is no error.
    // this allows you to pass `this.onerror`
    // to node-style callbacks.
    if (null == err) return;

    if (!(err instanceof Error)) err = new Error(util.format('non-error thrown: %j', err));

    let headerSent = false;
    if (this.headerSent || !this.writable) {
      headerSent = err.headerSent = true;
    }

    // delegate
    this.app.emit('error', err, this);

    // nothing we can do here other
    // than delegate to the app-level
    // handler and log.
    if (headerSent) {
      return;
    }

    const { res } = this;

    // first unset all headers
    /* istanbul ignore else */
    if (typeof res.getHeaderNames === 'function') {
      res.getHeaderNames().forEach(name => res.removeHeader(name));
    } else {
      res._headers = {}; // Node < 7.7
    }

    // then set those specified
    this.set(err.headers);

    // force text/plain
    this.type = 'text';

    // ENOENT support
    if ('ENOENT' == err.code) err.status = 404;

    // default to 500
    if ('number' != typeof err.status || !statuses[err.status]) err.status = 500;

    // respond
    const code = statuses[err.status];
    const msg = err.expose ? err.message : code;
    this.status = err.status;
    this.length = Buffer.byteLength(msg);
    res.end(msg);
  }

注释还是很清楚,是不是有点似曾相识的味道,就是同样会emit带着err到app的onerror这个娘家上。综上。。就了解context.onerror和app的’error‘event事件了吧。总的来说就是如果没有复写context下,在请求时发生的异常会抛到app.on('error',func)上。也算是了解一遭了,小插曲结束。
继续上面的话题,继续探讨koa-onerror的处理


可以看到koa.context已经对异常的处理了,并且最后会发送出去,koa-onerror之所以对onerror复写我想就是因为要“对症下药”,判断究竟是json\html还是其他的接收,是不是很眼熟,对,就是文章开头的代码尾部的处理,而我们能看到精美的egg错误页面也是在这里。这里通过对错误的截取渲染相关页面模板,并且输送出去。

至此,egg的异常结果页面就产生啦。

对于异常的解析,拆分,未完待续。。先改bug了。。稍后打卡更新

相关文章

网友评论

      本文标题:egg框架的异常返回页面的生成

      本文链接:https://www.haomeiwen.com/subject/jegehctx.html