app.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. 'use strict';
  2. const http = require('http');
  3. const fs = require('fs');
  4. const onerror = require('koa-onerror');
  5. const ErrorView = require('./lib/error_view');
  6. const {
  7. isProd,
  8. detectStatus,
  9. detectErrorMessage,
  10. accepts,
  11. } = require('./lib/utils');
  12. module.exports = app => {
  13. // logging error
  14. const config = app.config.onerror;
  15. const viewTemplate = fs.readFileSync(config.templatePath, 'utf8');
  16. app.on('error', (err, ctx) => {
  17. ctx = ctx || app.createAnonymousContext();
  18. if (config.appErrorFilter && !config.appErrorFilter(err, ctx)) return;
  19. const status = detectStatus(err);
  20. // 5xx
  21. if (status >= 500) {
  22. try {
  23. ctx.logger.error(err);
  24. } catch (ex) {
  25. app.logger.error(err);
  26. app.logger.error(ex);
  27. }
  28. return;
  29. }
  30. // 4xx
  31. try {
  32. ctx.logger.warn(err);
  33. } catch (ex) {
  34. app.logger.warn(err);
  35. app.logger.error(ex);
  36. }
  37. });
  38. const errorOptions = {
  39. // support customize accepts function
  40. accepts() {
  41. const fn = config.accepts || accepts;
  42. return fn(this);
  43. },
  44. html(err) {
  45. const status = detectStatus(err);
  46. const errorPageUrl = typeof config.errorPageUrl === 'function'
  47. ? config.errorPageUrl(err, this)
  48. : config.errorPageUrl;
  49. // keep the real response status
  50. this.realStatus = status;
  51. // don't respond any error message in production env
  52. if (isProd(app)) {
  53. // 5xx
  54. if (status >= 500) {
  55. if (errorPageUrl) {
  56. const statusQuery =
  57. (errorPageUrl.indexOf('?') > 0 ? '&' : '?') +
  58. `real_status=${status}`;
  59. return this.redirect(errorPageUrl + statusQuery);
  60. }
  61. this.status = 500;
  62. this.body = `<h2>Internal Server Error, real status: ${status}</h2>`;
  63. return;
  64. }
  65. // 4xx
  66. this.status = status;
  67. this.body = `<h2>${status} ${http.STATUS_CODES[status]}</h2>`;
  68. return;
  69. }
  70. // show simple error format for unittest
  71. if (app.config.env === 'unittest') {
  72. this.status = status;
  73. this.body = `${err.name}: ${err.message}\n${err.stack}`;
  74. return;
  75. }
  76. const errorView = new ErrorView(this, err, viewTemplate);
  77. this.body = errorView.toHTML();
  78. },
  79. json(err) {
  80. const status = detectStatus(err);
  81. let errorJson = {};
  82. this.status = status;
  83. const code = err.code || err.type;
  84. const message = detectErrorMessage(this, err);
  85. if (isProd(app)) {
  86. // 5xx server side error
  87. if (status >= 500) {
  88. errorJson = {
  89. code,
  90. // don't respond any error message in production env
  91. message: http.STATUS_CODES[status],
  92. };
  93. } else {
  94. // 4xx client side error
  95. // addition `errors`
  96. errorJson = {
  97. code,
  98. message,
  99. errors: err.errors,
  100. };
  101. }
  102. } else {
  103. errorJson = {
  104. code,
  105. message,
  106. errors: err.errors,
  107. };
  108. if (status >= 500) {
  109. // provide detail error stack in local env
  110. errorJson.stack = err.stack;
  111. errorJson.name = err.name;
  112. for (const key in err) {
  113. if (!errorJson[key]) {
  114. errorJson[key] = err[key];
  115. }
  116. }
  117. }
  118. }
  119. this.body = errorJson;
  120. },
  121. js(err) {
  122. errorOptions.json.call(this, err, this);
  123. if (this.createJsonpBody) {
  124. this.createJsonpBody(this.body);
  125. }
  126. },
  127. };
  128. // support customize error response
  129. [ 'all', 'html', 'json', 'text', 'js' ].forEach(type => {
  130. if (config[type]) errorOptions[type] = config[type];
  131. });
  132. onerror(app, errorOptions);
  133. };