说来惭愧,业务到现在都是单点服务,且无上报,对于测试发现的问题只有同步库查日志(测试环境日志都很难找),萌生了异常上报的想法,说到异常,不由地说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, 我们先停留在这层做个简单分析。这层到底做了什么?简要概括如下
- 读取预配置,默认读取
./lib/onerror_page.mustache
的模板,并且通过config可以自定义配置信息 - 监听对
error
事件监听,并且执行动作。 - 构建errorOptions对象,完成对 accepts、html、json、js的处理handler方法。并且注入到config中,参考注释方法
- 传递调用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相关的东西...)
期初拿到这个问题,我脑子里面蹦出来的几点念想:
- 都知道,egg是个框架,egg框架里面对于app的引用自然很多,我该怎么找。
- (猜测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了。。稍后打卡更新
网友评论