Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 36
Initial implementation#9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "extends": "loopback" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| sudo: false | ||
| language: node_js | ||
| node_js: | ||
| - "0.10" | ||
| - "0.12" | ||
| - "4" | ||
| - "6" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| Copyright (c) IBM Corp. 2016. All Rights Reserved. | ||
| Node module: strong-error-handler | ||
| This project is licensed under the MIT License, full text below. | ||
| -------- | ||
| MIT license | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in | ||
| all copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| THE SOFTWARE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,72 @@ | ||
| # strong-error-handler | ||
| # strong-error-handler | ||
| Error handler for use in development (debug) and production environments. | ||
| - When run in production mode, error responses are purposely undetailed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos "undetailed" is not a word. I can make the change in a new PR if you agree to it. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I missed this during review, @richardpringle feel free to submit a PR and assign it to me for review. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @richardpringle yes please, let's make this change. | ||
| in order to prevent leaking sensitive information. | ||
| - When in debug mode, detailed information such as stack traces | ||
| are returned in the HTTP responses. | ||
| JSON is the only supported response format at this time. | ||
| *There are plans to support other formats such as Text, HTML, and XML.* | ||
| ## Install | ||
| ```bash | ||
| $ npm install strong-error-handler | ||
| ``` | ||
| ## Usage | ||
| In an express-based application: | ||
| ```js | ||
| var express = require('express'); | ||
| var errorHandler = require('strong-error-handler'); | ||
| var app = express(); | ||
| // setup your routes | ||
| app.use(errorHandler({ /* options, see below */ })); | ||
| app.listen(3000); | ||
| ``` | ||
| In LoopBack applications, add the following entry to your | ||
| `server/middleware.json` file. | ||
| ```json | ||
| { | ||
| "final:after": { | ||
| "strong-error-handler": { | ||
| "params": { | ||
| } | ||
| ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| ## Options | ||
| #### debug | ||
| `boolean`, defaults to `false`. | ||
| When enabled, HTTP responses include all error properties, including | ||
| sensitive data such as file paths, URLs and stack traces. | ||
| #### log | ||
| `boolean`, defaults to `true`. | ||
| When enabled, all errors are printed via `console.error`. | ||
| Customization of the log format is intentionally not allowed. If you would like | ||
| to use a different format/logger, disable this option and add your own custom | ||
| error-handling middleware. | ||
| ```js | ||
| app.use(myErrorLogger()); | ||
| app.use(errorHandler({ log: false })); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Copyright IBM Corp. 2016. All Rights Reserved. | ||
| // Node module: strong-error-handler | ||
| // This file is licensed under the MIT License. | ||
| // License text available at https://opensource.org/licenses/MIT | ||
| 'use strict'; | ||
| var httpStatus = require('http-status'); | ||
| module.exports = function buildResponseData(err, isDebugMode) { | ||
| if (Array.isArray(err) && isDebugMode) { | ||
| ||
| err = serializeArrayOfErrors(err); | ||
| } | ||
| var data = Object.create(null); | ||
| fillStatusCode(data, err); | ||
| ||
| if (typeof err !== 'object') { | ||
| data.statusCode = 500; | ||
| data.message = '' + err; | ||
| err = {}; | ||
| } | ||
| if (isDebugMode) { | ||
| fillDebugData(data, err); | ||
| } else if (data.statusCode >= 400 && data.statusCode <= 499) { | ||
| fillBadRequestError(data, err); | ||
| } else { | ||
| fillInternalError(data, err); | ||
| } | ||
| ||
| return data; | ||
| }; | ||
| function serializeArrayOfErrors(errors) { | ||
| var details = []; | ||
| for (var ix in errors) { | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah, that's a habit I picked long time ago. It makes it easier to do a search & replace, because i is found all over the place in different names, while ix is usually match only the property. It also gives a bit more context about the variable purpose (ix = abbreviation of index). | ||
| var err = errors[ix]; | ||
| if (typeof err !== 'object') { | ||
| details.push('' + err); | ||
| continue; | ||
| } | ||
| var data = {stack: err.stack}; | ||
| for (var p in err) { // eslint-disable-line one-var | ||
| data[p] = err[p]; | ||
| } | ||
| details.push(data); | ||
| } | ||
| return { | ||
| name: 'ArrayOfErrors', | ||
| message: 'Failed with multiple errors, ' + | ||
| 'see `details` for more information.', | ||
| details: details, | ||
| }; | ||
| } | ||
| function fillStatusCode(data, err) { | ||
| data.statusCode = err.statusCode || err.status; | ||
| if (!data.statusCode || data.statusCode < 400) | ||
| data.statusCode = 500; | ||
| } | ||
| function fillDebugData(data, err) { | ||
| for (var p in err) { | ||
| if ((p in data)) continue; | ||
| data[p] = err[p]; | ||
| } | ||
| // NOTE err.stack is not an enumerable property | ||
| data.stack = err.stack; | ||
| } | ||
| function fillBadRequestError(data, err) { | ||
| data.name = err.name; | ||
| data.message = err.message; | ||
| data.details = err.details; | ||
| } | ||
| function fillInternalError(data, err) { | ||
| data.message = httpStatus[data.statusCode] || 'Unknown Error'; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Copyright IBM Corp. 2016. All Rights Reserved. | ||
| // Node module: strong-error-handler | ||
| // This file is licensed under the MIT License. | ||
| // License text available at https://opensource.org/licenses/MIT | ||
| 'use strict'; | ||
| var buildResponseData = require('./data-builder'); | ||
| var debug = require('debug')('strong-error-handler'); | ||
| var format = require('util').format; | ||
| var logToConsole = require('./logger'); | ||
| var sendJson = require('./send-json'); | ||
| function noop() { | ||
| } | ||
| /** | ||
| * Create a middleware error handler function. | ||
| * | ||
| * @param {Object} options | ||
| * @returns {Function} | ||
| */ | ||
| exports = module.exports = function createStrongErrorHandler(options) { | ||
| options = options || {}; | ||
| debug('Initializing with options %j', options); | ||
| // Debugging mode is disabled by default. When turned on (in dev), | ||
| // all error properties (including) stack traces are sent in the response | ||
| var isDebugMode = options.debug; | ||
| // Log all errors via console.error (enabled by default) | ||
| var logError = options.log !== false ? logToConsole : noop; | ||
| return function strongErrorHandler(err, req, res, next) { | ||
| debug('Handling %s', err.stack || err); | ||
| logError(req, err); | ||
| if (res._header) { | ||
| debug('Response was already sent, closing the underlying connection'); | ||
| return req.socket.destroy(); | ||
| } | ||
| var data = buildResponseData(err, isDebugMode); | ||
| debug('Response status %s data %j', data.statusCode, data); | ||
| res.setHeader('X-Content-Type-Options', 'nosniff'); | ||
| res.statusCode = data.statusCode; | ||
| // TODO: negotiate the content-type, take into account options.defaultType | ||
| // For now, we always return JSON. See | ||
| // - https://github.com/strongloop/strong-error-handler/issues/4 | ||
| // - https://github.com/strongloop/strong-error-handler/issues/5 | ||
| // - https://github.com/strongloop/strong-error-handler/issues/6 | ||
| sendJson(res, data); | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright IBM Corp. 2016. All Rights Reserved. | ||
| // Node module: strong-error-handler | ||
| // This file is licensed under the MIT License. | ||
| // License text available at https://opensource.org/licenses/MIT | ||
| 'use strict'; | ||
| var format = require('util').format; | ||
| module.exports = function logToConsole(req, err) { | ||
| if (!Array.isArray(err)) { | ||
| console.error('Unhandled error for request %s %s: %s', | ||
| req.method, req.url, err.stack || err); | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-posting earlier comments & discussion. My response: Hmm, I think we may want to tweak that rule for the case like this. I personally find this style better: console.error('Unhandled array of errors for request %s %s\n',req.method,req.url,errors);// alternativelyconsole.error('Unhandled array of errors for request %s %s\n',req.method,req.url,errors);compared to this one, which takes too much vertical space to my taste: console.error('Unhandled array of errors for request %s %s\n',req.method,req.url,errors);Here is what I am proposing to add to the rule: Exception: when the arguments are only primitive values (strings, numbers) or variable references, one can collapse them on the same line. console.error('Unhandled array of errors for request %s %s\n',req.method,req.url,errors);Thoughts? Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 I like the first style better too personally. Add to style guide and we're good to go. ;) MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. | ||
| return; | ||
| } | ||
| var errors = err.map(formatError).join('\n'); | ||
| console.error('Unhandled array of errors for request %s %s\n', | ||
| req.method, req.url, errors); | ||
| }; | ||
| function formatError(err) { | ||
| return format('%s', err.stack || err); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // Copyright IBM Corp. 2016. All Rights Reserved. | ||
| // Node module: strong-error-handler | ||
| // This file is licensed under the MIT License. | ||
| // License text available at https://opensource.org/licenses/MIT | ||
| 'use strict'; | ||
| module.exports = function sendJson(res, data) { | ||
| var content = JSON.stringify({error: data}); | ||
| res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||
| res.end(content, 'utf-8'); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "strong-error-handler", | ||
| "description": "Error handler for use in development and production environments.", | ||
| "license": "MIT", | ||
| "version": "1.0.0", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/strongloop/strong-error-handler.git" | ||
| }, | ||
| "main": "lib/handler.js", | ||
| "scripts": { | ||
| "lint": "eslint .", | ||
| "test": "mocha", | ||
| "posttest": "npm run lint" | ||
| }, | ||
| "dependencies": { | ||
| "debug": "^2.2.0", | ||
| "http-status": "^0.2.2" | ||
| }, | ||
| "devDependencies": { | ||
| "chai": "^2.1.1", | ||
| "eslint": "^2.5.3", | ||
| "eslint-config-loopback": "^3.0.0", | ||
| "mocha": "^2.1.0", | ||
| "supertest": "^1.1.0" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move 0.12 above 0.10 and it'll be reverse chronologically ordered.