'use strict';
const http = require('http');
const fs = require('fs');
const onerror = require('koa-onerror');
const ErrorView = require('./lib/error_view');
const {
isProd,
detectStatus,
detectErrorMessage,
accepts,
} = require('./lib/utils');
module.exports = app => {
// logging error
const config = app.config.onerror;
const viewTemplate = fs.readFileSync(config.templatePath, 'utf8');
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);
}
});
const errorOptions = {
// support customize accepts function
accepts() {
const fn = config.accepts || accepts;
return fn(this);
},
html(err) {
const status = detectStatus(err);
const errorPageUrl = typeof config.errorPageUrl === 'function'
? config.errorPageUrl(err, this)
: config.errorPageUrl;
// keep the real response status
this.realStatus = status;
// don't respond any error message in production env
if (isProd(app)) {
// 5xx
if (status >= 500) {
if (errorPageUrl) {
const statusQuery =
(errorPageUrl.indexOf('?') > 0 ? '&' : '?') +
`real_status=${status}`;
return this.redirect(errorPageUrl + statusQuery);
}
this.status = 500;
this.body = `
Internal Server Error, real status: ${status}
`;
return;
}
// 4xx
this.status = status;
this.body = `${status} ${http.STATUS_CODES[status]}
`;
return;
}
// show simple error format for unittest
if (app.config.env === 'unittest') {
this.status = status;
this.body = `${err.name}: ${err.message}\n${err.stack}`;
return;
}
const errorView = new ErrorView(this, err, viewTemplate);
this.body = errorView.toHTML();
},
json(err) {
const status = detectStatus(err);
let errorJson = {};
this.status = status;
const code = err.code || err.type;
const message = detectErrorMessage(this, err);
if (isProd(app)) {
// 5xx server side error
if (status >= 500) {
errorJson = {
code,
// don't respond any error message in production env
message: http.STATUS_CODES[status],
};
} else {
// 4xx client side error
// addition `errors`
errorJson = {
code,
message,
errors: err.errors,
};
}
} else {
errorJson = {
code,
message,
errors: err.errors,
};
if (status >= 500) {
// provide detail error stack in local env
errorJson.stack = err.stack;
errorJson.name = err.name;
for (const key in err) {
if (!errorJson[key]) {
errorJson[key] = err[key];
}
}
}
}
this.body = errorJson;
},
js(err) {
errorOptions.json.call(this, err, this);
if (this.createJsonpBody) {
this.createJsonpBody(this.body);
}
},
};
// support customize error response
[ 'all', 'html', 'json', 'text', 'js' ].forEach(type => {
if (config[type]) errorOptions[type] = config[type];
});
onerror(app, errorOptions);
};