├── .eslintrc ├── test ├── fixtures │ ├── onerror │ │ ├── package.json │ │ ├── app │ │ │ ├── controller │ │ │ │ ├── user.js │ │ │ │ └── home.js │ │ │ └── router.js │ │ └── config │ │ │ └── config.default.js │ ├── agent-error │ │ ├── package.json │ │ └── agent.js │ ├── onerror-4xx │ │ ├── package.json │ │ ├── config │ │ │ └── config.default.js │ │ └── app │ │ │ ├── router.js │ │ │ └── controller │ │ │ ├── user.js │ │ │ └── home.js │ ├── mock-test-error │ │ └── package.json │ ├── onerror-ctx-error │ │ ├── package.json │ │ ├── app │ │ │ ├── extend │ │ │ │ └── context.js │ │ │ └── middleware │ │ │ │ └── trigger.js │ │ └── config │ │ │ └── config.default.js │ ├── onerror-custom-500 │ │ ├── package.json │ │ ├── config │ │ │ └── config.default.js │ │ └── app │ │ │ └── router.js │ ├── onerror-customize │ │ ├── package.json │ │ ├── app │ │ │ ├── controller │ │ │ │ ├── user.js │ │ │ │ └── home.js │ │ │ └── router.js │ │ └── config │ │ │ └── config.default.js │ ├── onerror-no-errorpage │ │ ├── package.json │ │ ├── config │ │ │ └── config.default.js │ │ └── app │ │ │ ├── router.js │ │ │ └── controller │ │ │ ├── user.js │ │ │ └── home.js │ ├── custom-listener-onerror │ │ ├── package.json │ │ ├── app │ │ │ └── router.js │ │ └── config │ │ │ └── config.default.js │ └── onerror-custom-template │ │ ├── package.json │ │ ├── app │ │ ├── controller │ │ │ ├── user.js │ │ │ └── home.js │ │ └── router.js │ │ ├── config │ │ └── config.default.js │ │ └── template.mustache └── onerror.test.js ├── .gitignore ├── agent.js ├── .github └── workflows │ ├── release.yml │ └── nodejs.yml ├── config └── config.default.js ├── lib ├── utils.js ├── error_view.js └── onerror_page.mustache ├── LICENSE ├── package.json ├── README.md ├── app.js └── CHANGELOG.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/agent-error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agent-error" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-4xx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-4xx" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | run/ 6 | -------------------------------------------------------------------------------- /test/fixtures/mock-test-error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mock-test-error" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-ctx-error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-ctx-error" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-500/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-custom-500" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-customize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-customize" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-no-errorpage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-no-errorpage" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/custom-listener-onerror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-listener-onerror" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onerror-customize-template" 3 | } 4 | -------------------------------------------------------------------------------- /agent.js: -------------------------------------------------------------------------------- 1 | module.exports = agent => { 2 | // should watch error event 3 | agent.on('error', err => { 4 | agent.coreLogger.error(err); 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/onerror-ctx-error/app/extend/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get userId() { 3 | throw new Error('you can`t get userId.'); 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/agent-error/agent.js: -------------------------------------------------------------------------------- 1 | module.exports = agent => { 2 | const done = agent.readyCallback(); 3 | setTimeout(() => { 4 | done(new Error('emit error')); 5 | }, 500); 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/onerror-ctx-error/app/middleware/trigger.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return async function(ctx, next) { 3 | await next(); 4 | ctx.logger.info('log something, then error happend.'); 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/onerror-ctx-error/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.middleware = [ 2 | 'trigger', 3 | ]; 4 | 5 | exports.keys = 'foo,bar'; 6 | 7 | exports.logger = { 8 | level: 'NONE', 9 | consoleLevel: 'NONE', 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/custom-listener-onerror/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', async ctx => { 3 | const err = new Error('mock error'); 4 | err.name = ctx.query.name || 'Error'; 5 | throw err; 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/onerror-no-errorpage/config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.onerror = { 4 | }; 5 | 6 | exports.logger = { 7 | level: 'NONE', 8 | consoleLevel: 'NONE', 9 | }; 10 | 11 | exports.keys = 'foo,bar'; 12 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-500/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.onerror = { 2 | errorPageUrl: (_, ctx) => ctx.errorPageUrl || '/500', 3 | }; 4 | 5 | exports.keys = 'foo,bar'; 6 | 7 | exports.logger = { 8 | level: 'NONE', 9 | consoleLevel: 'NONE', 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/onerror-no-errorpage/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', app.controller.home.index); 3 | app.get('/csrf', app.controller.home.csrf); 4 | app.post('/test', app.controller.home.test); 5 | app.get('/user.json', app.controller.user); 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/onerror-4xx/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.onerror = { 2 | errorPageUrl: 'https://eggjs.com/500.html', 3 | }; 4 | 5 | exports.logger = { 6 | consoleLevel: 'NONE', 7 | }; 8 | 9 | exports.keys = 'foo,bar'; 10 | 11 | exports.security = { 12 | csrf: false, 13 | }; 14 | -------------------------------------------------------------------------------- /test/fixtures/onerror-4xx/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', app.controller.home.index); 3 | app.get('/csrf', app.controller.home.csrf); 4 | app.post('/test', app.controller.home.test); 5 | app.get('/user', app.controller.user); 6 | app.get('/user.json', app.controller.user); 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/onerror/app/controller/user.js: -------------------------------------------------------------------------------- 1 | module.exports = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.status) { 4 | err.status = Number(ctx.query.status); 5 | } 6 | if (ctx.query.errors) { 7 | err.errors = ctx.query.errors; 8 | } 9 | throw err; 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/onerror/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.onerror = { 2 | errorPageUrl: 'https://eggjs.com/500.html', 3 | }; 4 | 5 | exports.logger = { 6 | level: 'NONE', 7 | consoleLevel: 'NONE', 8 | }; 9 | 10 | exports.keys = 'foo,bar'; 11 | 12 | exports.security = { 13 | csrf: false, 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /test/fixtures/onerror-4xx/app/controller/user.js: -------------------------------------------------------------------------------- 1 | module.exports = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.status) { 4 | err.status = Number(ctx.query.status); 5 | } 6 | if (ctx.query.errors) { 7 | err.errors = ctx.query.errors; 8 | } 9 | throw err; 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/onerror-customize/app/controller/user.js: -------------------------------------------------------------------------------- 1 | module.exports = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.status) { 4 | err.status = Number(ctx.query.status); 5 | } 6 | if (ctx.query.errors) { 7 | err.errors = ctx.query.errors; 8 | } 9 | throw err; 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/onerror-no-errorpage/app/controller/user.js: -------------------------------------------------------------------------------- 1 | module.exports = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.status) { 4 | err.status = Number(ctx.query.status); 5 | } 6 | if (ctx.query.errors) { 7 | err.errors = ctx.query.errors; 8 | } 9 | throw err; 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-template/app/controller/user.js: -------------------------------------------------------------------------------- 1 | module.exports = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.status) { 4 | err.status = Number(ctx.query.status); 5 | } 6 | if (ctx.query.errors) { 7 | err.errors = ctx.query.errors; 8 | } 9 | throw err; 10 | }; 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release for 2.x 2 | 3 | on: 4 | push: 5 | branches: [ 2.x ] 6 | 7 | jobs: 8 | release: 9 | name: Node.js 10 | uses: eggjs/github-actions/.github/workflows/node-release.yml@master 11 | secrets: 12 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 13 | GIT_TOKEN: ${{ secrets.GIT_TOKEN }} 14 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-template/config/config.default.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | exports.onerror = { 4 | templatePath: path.join(__dirname, '../template.mustache'), 5 | }; 6 | 7 | exports.logger = { 8 | level: 'NONE', 9 | consoleLevel: 'NONE', 10 | }; 11 | 12 | exports.keys = 'foo,bar'; 13 | 14 | exports.security = { 15 | csrf: false, 16 | }; 17 | -------------------------------------------------------------------------------- /test/fixtures/onerror-customize/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', app.controller.home.index); 3 | app.get('/csrf', app.controller.home.csrf); 4 | app.post('/test', app.controller.home.test); 5 | app.get('/user', app.controller.user); 6 | app.get('/user.json', app.controller.user); 7 | app.get('/jsonp', app.jsonp(), app.controller.home.jsonp); 8 | }; 9 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-template/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', app.controller.home.index); 3 | app.get('/csrf', app.controller.home.csrf); 4 | app.post('/test', app.controller.home.test); 5 | app.get('/user', app.controller.user); 6 | app.get('/user.json', app.controller.user); 7 | app.get('/jsonp', app.jsonp(), app.controller.home.jsonp); 8 | }; 9 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI for 2.x 2 | 3 | on: 4 | push: 5 | branches: [ 2.x ] 6 | pull_request: 7 | branches: [ 2.x ] 8 | 9 | jobs: 10 | Job: 11 | name: Node.js 12 | uses: node-modules/github-actions/.github/workflows/node-test.yml@master 13 | with: 14 | os: 'ubuntu-latest, macos-latest, windows-latest' 15 | version: '14, 16, 18, 20, 22' 16 | -------------------------------------------------------------------------------- /test/fixtures/onerror-customize/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.onerror = { 2 | errorPageUrl: 'https://eggjs.com/500.html', 3 | json(err, ctx) { 4 | ctx.body = { msg: 'error' }; 5 | ctx.status = 500; 6 | }, 7 | }; 8 | 9 | exports.logger = { 10 | level: 'NONE', 11 | consoleLevel: 'NONE', 12 | }; 13 | 14 | exports.keys = 'foo,bar'; 15 | 16 | exports.security = { 17 | csrf: false, 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/onerror/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/', app.controller.home.index); 3 | app.get('/unknownFile', app.controller.home.unknownFile); 4 | app.get('/csrf', app.controller.home.csrf); 5 | app.post('/test', app.controller.home.test); 6 | app.get('/user', app.controller.user); 7 | app.get('/user.json', app.controller.user); 8 | app.get('/jsonp', app.jsonp(), app.controller.home.jsonp); 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/custom-listener-onerror/config/config.default.js: -------------------------------------------------------------------------------- 1 | exports.onerror = { 2 | errorPageUrl: 'https://eggjs.com/500.html', 3 | appErrorFilter(err, ctx) { 4 | if (err.name === 'IgnoreError') return false; 5 | if (err.name === 'CustomError') { 6 | ctx.app.logger.error('error happened'); 7 | return false; 8 | } 9 | return true; 10 | }, 11 | }; 12 | 13 | exports.keys = 'foo,bar'; 14 | 15 | exports.logger = { 16 | level: 'NONE', 17 | consoleLevel: 'NONE', 18 | }; 19 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | exports.onerror = { 4 | // 5xx error will redirect to ${errorPageUrl} 5 | // won't redirect in local env 6 | errorPageUrl: '', 7 | // will excute `appErrorFilter` when emit an error in `app` 8 | // If `appErrorFilter` return false, egg-onerror won't log this error. 9 | // You can logging in `appErrorFilter` and return false to override the default error logging. 10 | appErrorFilter: null, 11 | // default template path 12 | templatePath: path.join(__dirname, '../lib/onerror_page.mustache'), 13 | }; 14 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-500/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.get('/mockerror', async () => { 3 | // eslint-disable-next-line 4 | hi.foo(); 5 | }); 6 | 7 | app.get('/mock4xx', async () => { 8 | const err = new Error('4xx error'); 9 | err.status = 400; 10 | throw err; 11 | }); 12 | 13 | app.get('/500', async ctx => { 14 | ctx.status = 500; 15 | ctx.body = 'hi, this custom 500 page'; 16 | }); 17 | 18 | app.get('/special', async ctx => { 19 | ctx.errorPageUrl = '/specialerror'; 20 | // eslint-disable-next-line 21 | hi.foo(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/onerror-4xx/app/controller/home.js: -------------------------------------------------------------------------------- 1 | exports.index = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.code) { 4 | err.code = ctx.query.code; 5 | } 6 | if (ctx.query.status) { 7 | err.status = Number(ctx.query.status); 8 | } 9 | if (ctx.query.message) { 10 | err.message = ctx.query.message; 11 | } 12 | throw err; 13 | }; 14 | 15 | exports.csrf = async ctx => { 16 | ctx.set('x-csrf', ctx.csrf); 17 | ctx.body = 'test'; 18 | }; 19 | 20 | exports.test = async ctx => { 21 | const err = new SyntaxError('syntax error'); 22 | if (ctx.query.status) { 23 | err.status = Number(ctx.query.status); 24 | } 25 | throw err; 26 | }; 27 | -------------------------------------------------------------------------------- /test/fixtures/onerror-no-errorpage/app/controller/home.js: -------------------------------------------------------------------------------- 1 | exports.index = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.code) { 4 | err.code = ctx.query.code; 5 | } 6 | if (ctx.query.status) { 7 | err.status = Number(ctx.query.status); 8 | } 9 | if (ctx.query.message) { 10 | err.message = ctx.query.message; 11 | } 12 | throw err; 13 | }; 14 | 15 | exports.csrf = async ctx => { 16 | ctx.set('x-csrf', ctx.csrf); 17 | ctx.body = 'test'; 18 | }; 19 | 20 | exports.test = async ctx => { 21 | const err = new SyntaxError('syntax error'); 22 | if (ctx.query.status) { 23 | err.status = Number(ctx.query.status); 24 | } 25 | throw err; 26 | }; 27 | -------------------------------------------------------------------------------- /test/fixtures/onerror-customize/app/controller/home.js: -------------------------------------------------------------------------------- 1 | exports.index = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.code) { 4 | err.code = ctx.query.code; 5 | } 6 | if (ctx.query.status) { 7 | err.status = Number(ctx.query.status); 8 | } 9 | if (ctx.query.message) { 10 | err.message = ctx.query.message; 11 | } 12 | throw err; 13 | }; 14 | 15 | exports.csrf = async ctx => { 16 | ctx.set('x-csrf', ctx.csrf); 17 | ctx.body = 'test'; 18 | }; 19 | 20 | exports.test = async ctx => { 21 | const err = new SyntaxError('syntax error'); 22 | if (ctx.query.status) { 23 | err.status = Number(ctx.query.status); 24 | } 25 | throw err; 26 | }; 27 | 28 | exports.jsonp = async () => { 29 | throw new Error('jsonp error'); 30 | }; 31 | -------------------------------------------------------------------------------- /test/fixtures/onerror-custom-template/app/controller/home.js: -------------------------------------------------------------------------------- 1 | exports.index = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.code) { 4 | err.code = ctx.query.code; 5 | } 6 | if (ctx.query.status) { 7 | err.status = Number(ctx.query.status); 8 | } 9 | if (ctx.query.message) { 10 | err.message = ctx.query.message; 11 | } 12 | throw err; 13 | }; 14 | 15 | exports.csrf = async ctx => { 16 | ctx.set('x-csrf', ctx.csrf); 17 | ctx.body = 'test'; 18 | }; 19 | 20 | exports.test = async ctx => { 21 | const err = new SyntaxError('syntax error'); 22 | if (ctx.query.status) { 23 | err.status = Number(ctx.query.status); 24 | } 25 | throw err; 26 | }; 27 | 28 | exports.jsonp = async () => { 29 | throw new Error('jsonp error'); 30 | }; 31 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | exports.detectErrorMessage = function(ctx, err) { 2 | // detect json parse error 3 | if (err.status === 400 && 4 | err.name === 'SyntaxError' && 5 | ctx.request.is('application/json', 'application/vnd.api+json', 'application/csp-report')) { 6 | return 'Problems parsing JSON'; 7 | } 8 | return err.message; 9 | }; 10 | 11 | exports.detectStatus = function(err) { 12 | // detect status 13 | let status = err.status || 500; 14 | if (status < 200) { 15 | // invalid status consider as 500, like urllib will return -1 status 16 | status = 500; 17 | } 18 | return status; 19 | }; 20 | 21 | exports.accepts = function(ctx) { 22 | if (ctx.acceptJSON) return 'json'; 23 | if (ctx.acceptJSONP) return 'js'; 24 | return 'html'; 25 | }; 26 | 27 | exports.isProd = function(app) { 28 | return app.config.env !== 'local' && app.config.env !== 'unittest'; 29 | }; 30 | -------------------------------------------------------------------------------- /test/fixtures/onerror/app/controller/home.js: -------------------------------------------------------------------------------- 1 | exports.index = async ctx => { 2 | const err = new Error('test error'); 3 | if (ctx.query.code) { 4 | err.code = ctx.query.code; 5 | } 6 | if (ctx.query.status) { 7 | err.status = Number(ctx.query.status); 8 | } 9 | if (ctx.query.message) { 10 | err.message = ctx.query.message; 11 | } 12 | throw err; 13 | }; 14 | 15 | exports.unknownFile = async () => { 16 | const err = new Error('test error'); 17 | err.stack = err.stack.replace(/(controller\/home\.)js/, '$1ts'); 18 | throw err; 19 | }; 20 | 21 | exports.csrf = async ctx => { 22 | ctx.set('x-csrf', ctx.csrf); 23 | ctx.body = 'test'; 24 | }; 25 | 26 | exports.test = async ctx => { 27 | const err = new SyntaxError('syntax error'); 28 | if (ctx.query.status) { 29 | err.status = Number(ctx.query.status); 30 | } 31 | throw err; 32 | }; 33 | 34 | exports.jsonp = async () => { 35 | throw new Error('jsonp error'); 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present Alibaba Group Holding Limited and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-onerror", 3 | "version": "2.4.0", 4 | "description": "error handler for egg", 5 | "eggPlugin": { 6 | "name": "onerror", 7 | "optionalDependencies": [ 8 | "jsonp" 9 | ] 10 | }, 11 | "files": [ 12 | "config", 13 | "lib", 14 | "app.js", 15 | "agent.js" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/eggjs/onerror.git" 20 | }, 21 | "keywords": [ 22 | "egg", 23 | "egg-plugin", 24 | "onerror" 25 | ], 26 | "dependencies": { 27 | "cookie": "^0.7.2", 28 | "koa-onerror": "^4.0.0", 29 | "mustache": "^2.3.0", 30 | "stack-trace": "^0.0.10" 31 | }, 32 | "devDependencies": { 33 | "egg": "^3.7.0", 34 | "egg-bin": "^5.5.0", 35 | "egg-mock": "^5.3.0", 36 | "eslint": "^8.29.0", 37 | "eslint-config-egg": "^12.1.0", 38 | "mocha": "^10.7.3" 39 | }, 40 | "engines": { 41 | "node": ">=8.0.0" 42 | }, 43 | "scripts": { 44 | "test": "npm run lint -- --fix && npm run test-local", 45 | "test-local": "egg-bin test", 46 | "cov": "egg-bin cov", 47 | "lint": "eslint .", 48 | "ci": "npm run lint && npm run cov" 49 | }, 50 | "author": "dead_horse" 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-onerror 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [](https://github.com/eggjs/egg-onerror/actions/workflows/nodejs.yml) 5 | [![Test coverage][codecov-image]][codecov-url] 6 | [![Known Vulnerabilities][snyk-image]][snyk-url] 7 | [![npm download][download-image]][download-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/egg-onerror.svg?style=flat-square 10 | [npm-url]: https://npmjs.org/package/egg-onerror 11 | [codecov-image]: https://codecov.io/github/eggjs/egg-onerror/coverage.svg?branch=master 12 | [codecov-url]: https://codecov.io/github/eggjs/egg-onerror?branch=master 13 | [snyk-image]: https://snyk.io/test/npm/egg-onerror/badge.svg?style=flat-square 14 | [snyk-url]: https://snyk.io/test/npm/egg-onerror 15 | [download-image]: https://img.shields.io/npm/dm/egg-onerror.svg?style=flat-square 16 | [download-url]: https://npmjs.org/package/egg-onerror 17 | 18 | Default error handling plugin for egg. 19 | 20 | ## Install 21 | 22 | ```bash 23 | npm i egg-onerror@2 24 | ``` 25 | 26 | ## Usage 27 | 28 | `egg-onerror` is on by default in egg. But you still can configure its properties to fits your scenarios. 29 | 30 | - `errorPageUrl: String or Function` - If user request html pages in production environment and unexpected error happened, it will redirect user to `errorPageUrl`. 31 | - `accepts: Function` - detect user's request accept `json` or `html`. 32 | - `all: Function` - customize error handler, if `all` present, negotiation will be ignored. 33 | - `html: Function` - customize html error handler. 34 | - `text: Function` - customize text error handler. 35 | - `json: Function` - customize json error handler. 36 | - `jsonp: Function` - customize jsonp error handler. 37 | 38 | ```js 39 | // config.default.js 40 | // errorPageUrl support function 41 | exports.onerror = { 42 | errorPageUrl: (err, ctx) => ctx.errorPageUrl || '/500', 43 | }; 44 | 45 | // an accept detect function that mark all request with `x-requested-with=XMLHttpRequest` header accepts json. 46 | function accepts(ctx) { 47 | if (ctx.get('x-requested-with') === 'XMLHttpRequest') return 'json'; 48 | return 'html'; 49 | } 50 | ``` 51 | 52 | ## Questions & Suggestions 53 | 54 | Please open an issue [here](https://github.com/eggjs/egg/issues). 55 | 56 | ## License 57 | 58 | [MIT](https://github.com/eggjs/egg-onerror/blob/master/LICENSE) 59 | 60 | ## Contributors 61 | 62 | [](https://github.com/eggjs/egg-onerror/graphs/contributors) 63 | 64 | Made with [contributors-img](https://contrib.rocks). 65 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const onerror = require('koa-onerror'); 4 | const ErrorView = require('./lib/error_view'); 5 | const { 6 | isProd, 7 | detectStatus, 8 | detectErrorMessage, 9 | accepts, 10 | } = require('./lib/utils'); 11 | 12 | module.exports = app => { 13 | // logging error 14 | const config = app.config.onerror; 15 | const viewTemplate = fs.readFileSync(config.templatePath, 'utf8'); 16 | 17 | app.on('error', (err, ctx) => { 18 | if (!ctx) { 19 | ctx = app.currentContext || app.createAnonymousContext(); 20 | } 21 | if (config.appErrorFilter && !config.appErrorFilter(err, ctx)) return; 22 | 23 | const status = detectStatus(err); 24 | // 5xx 25 | if (status >= 500) { 26 | try { 27 | ctx.logger.error(err); 28 | } catch (ex) { 29 | app.logger.error(err); 30 | app.logger.error(ex); 31 | } 32 | return; 33 | } 34 | 35 | // 4xx 36 | try { 37 | ctx.logger.warn(err); 38 | } catch (ex) { 39 | app.logger.warn(err); 40 | app.logger.error(ex); 41 | } 42 | }); 43 | 44 | const errorOptions = { 45 | // support customize accepts function 46 | accepts() { 47 | const fn = config.accepts || accepts; 48 | return fn(this); 49 | }, 50 | 51 | html(err) { 52 | const status = detectStatus(err); 53 | const errorPageUrl = typeof config.errorPageUrl === 'function' 54 | ? config.errorPageUrl(err, this) 55 | : config.errorPageUrl; 56 | 57 | // keep the real response status 58 | this.realStatus = status; 59 | // don't respond any error message in production env 60 | if (isProd(app)) { 61 | // 5xx 62 | if (status >= 500) { 63 | if (errorPageUrl) { 64 | const statusQuery = 65 | (errorPageUrl.indexOf('?') > 0 ? '&' : '?') + 66 | `real_status=${status}`; 67 | return this.redirect(errorPageUrl + statusQuery); 68 | } 69 | this.status = 500; 70 | this.body = `
{{ appInfo.config }}
112 | {{ appInfo.config }}
677 |