說(shuō)來(lái)慚愧,業(yè)務(wù)到現(xiàn)在都是單點(diǎn)服務(wù)翔烁,且無(wú)上報(bào)匾灶,對(duì)于測(cè)試發(fā)現(xiàn)的問(wèn)題只有同步庫(kù)查日志(測(cè)試環(huán)境日志都很難找),萌生了異常上報(bào)的想法租漂,說(shuō)到異常阶女,不由地說(shuō)egg的錯(cuò)誤頁(yè)面,多么精致哩治,今天就來(lái)分析一下頁(yè)面的產(chǎn)生
egg-onerror的處理
egg框架在egg包中調(diào)用了egg-onerror包秃踩,并且開(kāi)啟了這個(gè)plugin。進(jìn)入看一下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);
}
可以看到這貨接下來(lái)調(diào)用的是koa-onerror, 我們先停留在這層做個(gè)簡(jiǎn)單分析业筏。這層到底做了什么憔杨?簡(jiǎn)要概括如下
- 讀取預(yù)配置,默認(rèn)讀取
./lib/onerror_page.mustache
的模板蒜胖,并且通過(guò)config可以自定義配置信息 - 監(jiān)聽(tīng)對(duì)
error
事件監(jiān)聽(tīng)消别,并且執(zhí)行動(dòng)作。 - 構(gòu)建errorOptions對(duì)象台谢,完成對(duì) accepts寻狂、html、json朋沮、js的處理handler方法蛇券。并且注入到config中,參考注釋方法
- 傳遞調(diào)用koa-onerror方法
關(guān)于app的onerror事件,是對(duì)狀態(tài)碼判斷并輸送至指定日志對(duì)象上纠亚,輸出日志作用塘慕。這里是對(duì)處理的一個(gè)截?cái)啵覀兝^續(xù)往下看koa-onerror
koa-onerror的處理
這里的處理全部在app.context.onerror的對(duì)象方法賦值上. 那么問(wèn)題來(lái)了蒂胞,app的app.on('error',func)
和app.context.onerror
有what子區(qū)別图呢。
咳咳咳,先暫停一下骗随,小的有事稟報(bào)...
插曲1:分析 app.on('error',func) 和 app.context.onerror 的淺析
(ps: 這個(gè)時(shí)候我來(lái)沒(méi)看過(guò)koa相關(guān)的東西...)
期初拿到這個(gè)問(wèn)題岳瞭,我腦子里面蹦出來(lái)的幾點(diǎn)念想:
- 都知道,egg是個(gè)框架蚊锹,egg框架里面對(duì)于app的引用自然很多,我該怎么找稚瘾。
- (猜測(cè)1)指不定onerror就是某個(gè)文件的某個(gè)地方是
app.on('xxxx',func)
的func
對(duì)象呢牡昆?(猜測(cè)2)event事件會(huì)不會(huì)是個(gè)截?cái)啵蜕衔念?lèi)似摊欠?
接著往下看丢烘,剛才koa-onerror的代碼某處有一點(diǎn):koa-onerror/index.js
app.context.onerror = function(err) {
...
this.app.emit('error', err, this);
...
}
就是說(shuō)一個(gè)context.onerror是個(gè)方法,另外這行代碼有點(diǎn)刺眼些椒。是方法總有地方會(huì)調(diào)用吧播瞳,其二,為什么會(huì)在一個(gè)onerror單詞emit執(zhí)行了error的event事件免糕,且將自己的err參數(shù)發(fā)過(guò)去了赢乓,明白這是一個(gè)主動(dòng)觸發(fā)。context哪來(lái)的石窑,egg.ctx給的牌芋。egg哪來(lái)的?koa...(說(shuō)npm下載下來(lái)的準(zhǔn)備開(kāi)打了...)
koa的里面有application.js松逊、context.js躺屁、requset\response.js, ...(中間省略幾行我傻不拉幾去找eggApplication的error處理的過(guò)程), 大家都是到koa是怎么調(diào)用的, KOA以下代碼來(lái)自官文,不解釋了经宏。
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
是app.listen
對(duì)吧犀暑,OK, 我們看下KOA的koa/application.js
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
返回調(diào)用在this.callback()
上,繼續(xù)追進(jìn)去
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
就不解釋了烁兰,返回監(jiān)聽(tīng)這個(gè)事件的數(shù)量耐亏,Application是繼承Emitter的,因此這里判斷沪斟,如果沒(méi)有error事件監(jiān)聽(tīng)苹熏,就丟給了this.onerror,也就是application.onerror(注意這里對(duì)象是app)。這里還不是context的轨域,很容易理解啊袱耽,在其位謀其政,app的onerror是處理平臺(tái)級(jí)的錯(cuò)誤信息干发。繼續(xù)看handleRequest
是不是就找到了context.onerror了朱巨,當(dāng)然走到這步了就繼續(xù)看看koa.context對(duì)onerror的實(shí)現(xiàn)
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);
}
注釋還是很清楚,是不是有點(diǎn)似曾相識(shí)的味道枉长,就是同樣會(huì)emit帶著err到app的onerror這個(gè)娘家上冀续。綜上。必峰。就了解context.onerror和app的’error‘event事件了吧洪唐。總的來(lái)說(shuō)就是如果沒(méi)有復(fù)寫(xiě)context下吼蚁,在請(qǐng)求時(shí)發(fā)生的異常會(huì)拋到app.on('error',func)
上凭需。也算是了解一遭了,小插曲結(jié)束肝匆。
繼續(xù)上面的話題粒蜈,繼續(xù)探討koa-onerror的處理
可以看到koa.context已經(jīng)對(duì)異常的處理了,并且最后會(huì)發(fā)送出去旗国,koa-onerror之所以對(duì)onerror復(fù)寫(xiě)我想就是因?yàn)橐皩?duì)癥下藥”枯怖,判斷究竟是json\html還是其他的接收,是不是很眼熟能曾,對(duì)度硝,就是文章開(kāi)頭的代碼尾部的處理,而我們能看到精美的egg錯(cuò)誤頁(yè)面也是在這里寿冕。這里通過(guò)對(duì)錯(cuò)誤的截取渲染相關(guān)頁(yè)面模板塘淑,并且輸送出去。
至此蚂斤,egg的異常結(jié)果頁(yè)面就產(chǎn)生啦存捺。
對(duì)于異常的解析,拆分曙蒸,未完待續(xù)捌治。。先改bug了纽窟。肖油。稍后打卡更新