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

说来惭愧,业务到现在都是单点服务,且无上报,对于测试发现的问题只有同步库查日志(测试环境日志都很难找),萌生了异常上报的想法,说到异常,不由地说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了。。稍后打卡更新

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容