'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); };