strong-error-handler 1.0.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE.md +36 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +24 -0
- package/CHANGES.md +54 -0
- package/README.md +157 -26
- package/intl/de/messages.json +5 -0
- package/intl/en/messages.json +4 -0
- package/intl/es/messages.json +5 -0
- package/intl/fr/messages.json +5 -0
- package/intl/it/messages.json +5 -0
- package/intl/ja/messages.json +5 -0
- package/intl/ko/messages.json +5 -0
- package/intl/nl/messages.json +5 -0
- package/intl/pt/messages.json +5 -0
- package/intl/tr/messages.json +5 -0
- package/intl/zh-Hans/messages.json +5 -0
- package/intl/zh-Hant/messages.json +5 -0
- package/lib/clone.js +1 -0
- package/lib/content-negotiation.js +91 -0
- package/lib/data-builder.js +18 -1
- package/lib/handler.js +7 -12
- package/lib/logger.js +5 -3
- package/lib/send-html.js +47 -0
- package/lib/send-xml.js +14 -0
- package/package.json +17 -4
- package/views/default-error.ejs +25 -0
- package/views/style.css +31 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- Please ask questions at https://groups.google.com/forum/#!forum/loopbackjs or
|
|
3
|
+
https://gitter.im/strongloop/loopback
|
|
4
|
+
|
|
5
|
+
- Immediate support is available through our subscription plans, see
|
|
6
|
+
https://strongloop.com/api-connect-faqs/
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
### Bug or feature request
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
Mark your choice with an "x" (eg. [x], NOT [*]).
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
- [ ] Bug
|
|
16
|
+
- [ ] Feature request
|
|
17
|
+
|
|
18
|
+
### Description of feature (or steps to reproduce if bug)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Link to sample repo to reproduce issue (if bug)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Expected result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Actual result (if bug)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Additional information (Node.js version, LoopBack version, etc)
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
### Description
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#### Related issues
|
|
5
|
+
|
|
6
|
+
<!--
|
|
7
|
+
Please use the following link syntaxes:
|
|
8
|
+
|
|
9
|
+
- #49 (to reference issues in the current repository)
|
|
10
|
+
- strongloop/loopback#49 (to reference issues in another repository)
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
- None
|
|
14
|
+
|
|
15
|
+
### Checklist
|
|
16
|
+
|
|
17
|
+
<!--
|
|
18
|
+
Please mark your choice with an "x" (i.e. [x], see
|
|
19
|
+
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
- [ ] New tests added or existing tests modified to cover all changes
|
|
23
|
+
- [ ] Code conforms with the [style
|
|
24
|
+
guide](http://loopback.io/doc/en/contrib/style-guide.html)
|
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
2017-01-30, Version 1.2.1
|
|
2
|
+
=========================
|
|
3
|
+
|
|
4
|
+
* Stop adding safeFields to original options arg (Miroslav Bajtoš)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
2017-01-30, Version 1.2.0
|
|
8
|
+
=========================
|
|
9
|
+
|
|
10
|
+
* Support options.safeFields (Zak Barbuto)
|
|
11
|
+
|
|
12
|
+
* Readme cleanup (#36) (Rand McKinney)
|
|
13
|
+
|
|
14
|
+
* xml support added (Ahmet Ozisik)
|
|
15
|
+
|
|
16
|
+
* Update paid support URL (Siddhi Pai)
|
|
17
|
+
|
|
18
|
+
* Downstream ignore dashboard-controller (Simon Ho)
|
|
19
|
+
|
|
20
|
+
* Update pt translation file (Candy)
|
|
21
|
+
|
|
22
|
+
* Make the examples more clear (Amir Jafarian)
|
|
23
|
+
|
|
24
|
+
* Fix readme (Amir Jafarian)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
2016-10-07, Version 1.1.1
|
|
28
|
+
=========================
|
|
29
|
+
|
|
30
|
+
* Update pt translation file (Candy)
|
|
31
|
+
|
|
32
|
+
* Update translation files - round#2 (Candy)
|
|
33
|
+
|
|
34
|
+
* globalization: add translated strings (gunjpan)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
2016-09-05, Version 1.1.0
|
|
38
|
+
=========================
|
|
39
|
+
|
|
40
|
+
* Fix minor Syntax error (Loay)
|
|
41
|
+
|
|
42
|
+
* Globalize strong-error-handler (David Cheung)
|
|
43
|
+
|
|
44
|
+
* Update eslint infrastructure (Loay)
|
|
45
|
+
|
|
46
|
+
* Add documentation (Loay)
|
|
47
|
+
|
|
48
|
+
* Improve grammar in readme. (Richard Pringle)
|
|
49
|
+
|
|
50
|
+
* Test with express instead of http server (David Cheung)
|
|
51
|
+
|
|
52
|
+
* HTML response for accepted headers (David Cheung)
|
|
53
|
+
|
|
54
|
+
|
|
1
55
|
2016-05-26, Version 1.0.1
|
|
2
56
|
=========================
|
|
3
57
|
|
package/README.md
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# strong-error-handler
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package is an error handler for use in both development (debug) and production environments.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
in order to prevent leaking sensitive information.
|
|
5
|
+
In production mode, `strong-error-handler` omits details from error responses to prevent leaking sensitive information:
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
- For 5xx errors, the output contains only the status code and the status name from the HTTP specification.
|
|
8
|
+
- For 4xx errors, the output contains the full error message (`error.message`) and the contents of the `details`
|
|
9
|
+
property (`error.details`) that `ValidationError` typically uses to provide machine-readable details
|
|
10
|
+
about validation problems.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
In debug mode, `strong-error-handler` returns full error stack traces and internal details of any error objects to the client in the HTTP responses.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Install
|
|
14
|
+
## Installation
|
|
16
15
|
|
|
17
16
|
```bash
|
|
18
|
-
$ npm install strong-error-handler
|
|
17
|
+
$ npm install --save strong-error-handler
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
##
|
|
20
|
+
## Use
|
|
22
21
|
|
|
23
|
-
In an
|
|
22
|
+
In an Express-based application:
|
|
24
23
|
|
|
25
24
|
```js
|
|
26
25
|
var express = require('express');
|
|
@@ -28,45 +27,177 @@ var errorHandler = require('strong-error-handler');
|
|
|
28
27
|
|
|
29
28
|
var app = express();
|
|
30
29
|
// setup your routes
|
|
31
|
-
|
|
30
|
+
// `options` are set to default values. For more info, see `options` below.
|
|
31
|
+
// app.use(errorHandler({ /* options, see below */ }));
|
|
32
|
+
app.use(errorHandler({
|
|
33
|
+
debug: app.get('env') === 'development',
|
|
34
|
+
log: true,
|
|
35
|
+
}));
|
|
32
36
|
|
|
33
37
|
app.listen(3000);
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
In LoopBack applications, add the following entry to
|
|
37
|
-
`server/middleware.json` file.
|
|
40
|
+
In LoopBack applications, add the following entry to `server/middleware.json`:
|
|
38
41
|
|
|
39
42
|
```json
|
|
40
43
|
{
|
|
41
44
|
"final:after": {
|
|
42
45
|
"strong-error-handler": {
|
|
43
46
|
"params": {
|
|
47
|
+
"debug": false,
|
|
48
|
+
"log": true
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
In general, `strong-error-handler` must be the last middleware function registered.
|
|
56
|
+
|
|
57
|
+
The above configuration will log errors to the server console, but not return stack traces in HTTP responses.
|
|
58
|
+
For details on configuration options, see below.
|
|
59
|
+
|
|
60
|
+
### Response format and content type
|
|
61
|
+
|
|
62
|
+
The `strong-error-handler` package supports HTML and JSON responses:
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
- When the object is a standard Error object, it returns the string provided by the stack property in HTML/text
|
|
65
|
+
responses.
|
|
66
|
+
- When the object is a non-Error object, it returns the result of `util.inspect` in HTML/text responses.
|
|
67
|
+
- For JSON responses, the result is an object with all enumerable properties from the object in the response.
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
The content type of the response depends on the request's `Accepts` header.
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
- For Accepts header `json` or `application/json`, the response content type is JSON.
|
|
72
|
+
- For Accepts header `html` or `text/html`, the response content type is HTML.
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
*There are plans to support other formats such as Text and XML.*
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
## Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
| ---- | ---- | ---- | ---- |
|
|
80
|
+
| debug | Boolean | `false` | If `true`, HTTP responses include all error properties, including sensitive data such as file paths, URLs and stack traces. See [Example output](#example) below. |
|
|
81
|
+
| log | Boolean | `true` | If `true`, all errors are printed via `console.error`, including an array of fields (custom error properties) that are safe to include in response messages (both 4xx and 5xx). <br/> If `false`, sends only the error back in the response. |
|
|
82
|
+
|
|
83
|
+
### Customizing log format
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
**Express**
|
|
64
86
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
error-handling middleware.
|
|
87
|
+
To use a different log format, add your own custom error-handling middleware then disable `errorHandler.log`.
|
|
88
|
+
For example, in an Express application:
|
|
68
89
|
|
|
69
90
|
```js
|
|
70
91
|
app.use(myErrorLogger());
|
|
71
92
|
app.use(errorHandler({ log: false }));
|
|
72
93
|
```
|
|
94
|
+
|
|
95
|
+
In general, add `strong-error-handler` as the last middleware function, just before calling `app.listen()`.
|
|
96
|
+
|
|
97
|
+
**LoopBack**
|
|
98
|
+
|
|
99
|
+
For LoopBack applications, put custom error-logging middleware in a separate file; for example, `server/middleware/error-logger.js`:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
module.exports = function(options) {
|
|
103
|
+
return function logError(err, req, res, next) {
|
|
104
|
+
console.log('unhandled error' ,err);
|
|
105
|
+
next(err);
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Then in `server/middleware.json`, specify your custom error logging function as follows:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
{
|
|
114
|
+
// ...
|
|
115
|
+
"final:after": {
|
|
116
|
+
"./middleware/error-logger": {},
|
|
117
|
+
"strong-error-handler": {
|
|
118
|
+
"params": {
|
|
119
|
+
log: false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The default `middleware.development.json` file explicitly enables logging in strong-error-handler params, so you will need to change that file too.
|
|
126
|
+
|
|
127
|
+
## Migration from old LoopBack error handler
|
|
128
|
+
|
|
129
|
+
NOTE: This is only required for applications scaffolded with old versions of the `slc loopback` tool.
|
|
130
|
+
|
|
131
|
+
To migrate a LoopBack 2.x application to use `strong-error-handler`:
|
|
132
|
+
|
|
133
|
+
1. In `package.json` dependencies, remove `"errorhandler": "^x.x.x”,`
|
|
134
|
+
1. Install the new error handler by entering the command:
|
|
135
|
+
<pre>npm install --save strong-error-handler</pre>
|
|
136
|
+
1. In `server/config.json`, remove:
|
|
137
|
+
<pre>
|
|
138
|
+
"remoting": {
|
|
139
|
+
...
|
|
140
|
+
"errorHandler": {
|
|
141
|
+
"disableStackTrace": false
|
|
142
|
+
}</pre>
|
|
143
|
+
and replace it with:
|
|
144
|
+
<pre>
|
|
145
|
+
"remoting": {
|
|
146
|
+
...,
|
|
147
|
+
"rest": {
|
|
148
|
+
"handleErrors": false
|
|
149
|
+
}</pre>
|
|
150
|
+
1. In `server/middleware.json`, remove:
|
|
151
|
+
<pre>
|
|
152
|
+
"final:after": {
|
|
153
|
+
"loopback#errorHandler": {}
|
|
154
|
+
}</pre>
|
|
155
|
+
and replace it with:
|
|
156
|
+
<pre>
|
|
157
|
+
"final:after": {
|
|
158
|
+
"strong-error-handler": {}
|
|
159
|
+
}</pre>
|
|
160
|
+
1. Delete `server/middleware.production.json`.
|
|
161
|
+
1. Create `server/middleware.development.json` containing:
|
|
162
|
+
<pre>
|
|
163
|
+
"final:after": {
|
|
164
|
+
"strong-error-handler": {
|
|
165
|
+
"params": {
|
|
166
|
+
"debug": true,
|
|
167
|
+
"log": true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
</pre>
|
|
172
|
+
|
|
173
|
+
For more information, see
|
|
174
|
+
[Migrating apps to LoopBack 3.0](http://loopback.io/doc/en/lb3/Migrating-to-3.0.html#update-use-of-rest-error-handler).
|
|
175
|
+
|
|
176
|
+
## Example
|
|
177
|
+
|
|
178
|
+
Error generated when `debug: false` :
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
{ error: { statusCode: 500, message: 'Internal Server Error' } }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Error generated when `debug: true` :
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
{ error:
|
|
188
|
+
{ statusCode: 500,
|
|
189
|
+
name: 'Error',
|
|
190
|
+
message: 'a test error message',
|
|
191
|
+
stack: 'Error: a test error message
|
|
192
|
+
at Context.<anonymous> (User/strong-error-handler/test/handler.test.js:220:21)
|
|
193
|
+
at callFnAsync (User/strong-error-handler/node_modules/mocha/lib/runnable.js:349:8)
|
|
194
|
+
at Test.Runnable.run (User/strong-error-handler/node_modules/mocha/lib/runnable.js:301:7)
|
|
195
|
+
at Runner.runTest (User/strong-error-handler/node_modules/mocha/lib/runner.js:422:10)
|
|
196
|
+
at User/strong-error-handler/node_modules/mocha/lib/runner.js:528:12
|
|
197
|
+
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:342:14)
|
|
198
|
+
at User/strong-error-handler/node_modules/mocha/lib/runner.js:352:7
|
|
199
|
+
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:284:14)
|
|
200
|
+
at Immediate._onImmediate (User/strong-error-handler/node_modules/mocha/lib/runner.js:320:5)
|
|
201
|
+
at tryOnImmediate (timers.js:543:15)
|
|
202
|
+
at processImmediate [as _immediateCallback] (timers.js:523:5)' }}
|
|
203
|
+
```
|
package/lib/clone.js
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2016. All Rights Reserved.
|
|
2
|
+
// Node module: strong-error-handler
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
var accepts = require('accepts');
|
|
8
|
+
var debug = require('debug')('strong-error-handler:http-response');
|
|
9
|
+
var sendJson = require('./send-json');
|
|
10
|
+
var sendHtml = require('./send-html');
|
|
11
|
+
var sendXml = require('./send-xml');
|
|
12
|
+
var util = require('util');
|
|
13
|
+
|
|
14
|
+
module.exports = negotiateContentProducer;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles req.accepts and req.query._format and options.defaultType
|
|
18
|
+
* to resolve the correct operation
|
|
19
|
+
*
|
|
20
|
+
* @param req request object
|
|
21
|
+
* @param {Object} options options of strong-error-handler
|
|
22
|
+
* @returns {Function} Opeartion function with signature `fn(res, data)`
|
|
23
|
+
*/
|
|
24
|
+
function negotiateContentProducer(req, options) {
|
|
25
|
+
var SUPPORTED_TYPES = [
|
|
26
|
+
'application/json', 'json',
|
|
27
|
+
'text/html', 'html',
|
|
28
|
+
'text/xml', 'xml',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
options = options || {};
|
|
32
|
+
var defaultType = 'json';
|
|
33
|
+
|
|
34
|
+
// checking if user provided defaultType is supported
|
|
35
|
+
if (options.defaultType) {
|
|
36
|
+
if (SUPPORTED_TYPES.indexOf(options.defaultType) > -1) {
|
|
37
|
+
debug('Accepting options.defaultType `%s`', options.defaultType);
|
|
38
|
+
defaultType = options.defaultType;
|
|
39
|
+
} else {
|
|
40
|
+
debug('defaultType: `%s` is not supported, ' +
|
|
41
|
+
'falling back to defaultType: `%s`', options.defaultType, defaultType);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// decide to use resolvedType or defaultType
|
|
46
|
+
// Please note that accepts assumes the order of content-type is provided
|
|
47
|
+
// in the priority returned
|
|
48
|
+
// example
|
|
49
|
+
// Accepts: text/html, */*, application/json ---> will resolve as text/html
|
|
50
|
+
// Accepts: application/json, */*, text/html ---> will resolve as application/json
|
|
51
|
+
// Accepts: */*, application/json, text/html ---> will resolve as application/json
|
|
52
|
+
// eg. Chrome accepts defaults to `text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*`
|
|
53
|
+
// In this case `resolvedContentType` will result as: `text/html` due to the order given
|
|
54
|
+
var resolvedContentType = accepts(req).types(SUPPORTED_TYPES);
|
|
55
|
+
debug('Resolved content-type', resolvedContentType);
|
|
56
|
+
var contentType = resolvedContentType || defaultType;
|
|
57
|
+
|
|
58
|
+
// to receive _format from user's url param to overide the content type
|
|
59
|
+
// req.query (eg /api/Users/1?_format=json will overide content negotiation
|
|
60
|
+
// https://github.com/strongloop/strong-remoting/blob/ac3093dcfbb787977ca0229b0f672703859e52e1/lib/http-context.js#L643-L645
|
|
61
|
+
var query = req.query || {};
|
|
62
|
+
if (query._format) {
|
|
63
|
+
if (SUPPORTED_TYPES.indexOf(query._format) > -1) {
|
|
64
|
+
contentType = query._format;
|
|
65
|
+
} else {
|
|
66
|
+
// format passed through query but not supported
|
|
67
|
+
var msg = util.format('Response _format "%s" is not supported' +
|
|
68
|
+
'used "%s" instead"', query._format, defaultType);
|
|
69
|
+
res.header('X-Warning', msg);
|
|
70
|
+
debug(msg);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
debug('Content-negotiation: req.headers.accept: `%s` Resolved as: `%s`',
|
|
75
|
+
req.headers.accept, contentType);
|
|
76
|
+
return resolveOperation(contentType);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveOperation(contentType) {
|
|
80
|
+
switch (contentType) {
|
|
81
|
+
case 'application/json':
|
|
82
|
+
case 'json':
|
|
83
|
+
return sendJson;
|
|
84
|
+
case 'text/html':
|
|
85
|
+
case 'html':
|
|
86
|
+
return sendHtml;
|
|
87
|
+
case 'text/xml':
|
|
88
|
+
case 'xml':
|
|
89
|
+
return sendXml;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/lib/data-builder.js
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
var cloneAllProperties = require('../lib/clone.js');
|
|
9
9
|
var httpStatus = require('http-status');
|
|
10
10
|
|
|
11
|
-
module.exports = function buildResponseData(err,
|
|
11
|
+
module.exports = function buildResponseData(err, options) {
|
|
12
|
+
// Debugging mode is disabled by default. When turned on (in dev),
|
|
13
|
+
// all error properties (including) stack traces are sent in the response
|
|
14
|
+
var isDebugMode = options.debug;
|
|
15
|
+
|
|
12
16
|
if (Array.isArray(err) && isDebugMode) {
|
|
13
17
|
err = serializeArrayOfErrors(err);
|
|
14
18
|
}
|
|
@@ -31,6 +35,9 @@ module.exports = function buildResponseData(err, isDebugMode) {
|
|
|
31
35
|
fillInternalError(data, err);
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
var safeFields = options.safeFields || [];
|
|
39
|
+
fillSafeFields(data, err, safeFields);
|
|
40
|
+
|
|
34
41
|
return data;
|
|
35
42
|
};
|
|
36
43
|
|
|
@@ -76,3 +83,13 @@ function fillBadRequestError(data, err) {
|
|
|
76
83
|
function fillInternalError(data, err) {
|
|
77
84
|
data.message = httpStatus[data.statusCode] || 'Unknown Error';
|
|
78
85
|
}
|
|
86
|
+
|
|
87
|
+
function fillSafeFields(data, err, safeFields) {
|
|
88
|
+
if (!Array.isArray(safeFields)) {
|
|
89
|
+
safeFields = [safeFields];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
safeFields.forEach(function(field) {
|
|
93
|
+
data[field] = err[field];
|
|
94
|
+
});
|
|
95
|
+
}
|
package/lib/handler.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
var path = require('path');
|
|
9
|
+
var SG = require('strong-globalize');
|
|
10
|
+
SG.SetRootDir(path.resolve(__dirname, '..'));
|
|
8
11
|
var buildResponseData = require('./data-builder');
|
|
9
12
|
var debug = require('debug')('strong-error-handler');
|
|
10
13
|
var format = require('util').format;
|
|
11
14
|
var logToConsole = require('./logger');
|
|
12
|
-
var
|
|
15
|
+
var negotiateContentProducer = require('./content-negotiation');
|
|
13
16
|
|
|
14
17
|
function noop() {
|
|
15
18
|
}
|
|
@@ -25,10 +28,6 @@ exports = module.exports = function createStrongErrorHandler(options) {
|
|
|
25
28
|
|
|
26
29
|
debug('Initializing with options %j', options);
|
|
27
30
|
|
|
28
|
-
// Debugging mode is disabled by default. When turned on (in dev),
|
|
29
|
-
// all error properties (including) stack traces are sent in the response
|
|
30
|
-
var isDebugMode = options.debug;
|
|
31
|
-
|
|
32
31
|
// Log all errors via console.error (enabled by default)
|
|
33
32
|
var logError = options.log !== false ? logToConsole : noop;
|
|
34
33
|
|
|
@@ -46,17 +45,13 @@ exports = module.exports = function createStrongErrorHandler(options) {
|
|
|
46
45
|
if (!err.status && !err.statusCode && res.statusCode >= 400)
|
|
47
46
|
err.statusCode = res.statusCode;
|
|
48
47
|
|
|
49
|
-
var data = buildResponseData(err,
|
|
48
|
+
var data = buildResponseData(err, options);
|
|
50
49
|
debug('Response status %s data %j', data.statusCode, data);
|
|
51
50
|
|
|
52
51
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
53
52
|
res.statusCode = data.statusCode;
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// - https://github.com/strongloop/strong-error-handler/issues/4
|
|
58
|
-
// - https://github.com/strongloop/strong-error-handler/issues/5
|
|
59
|
-
// - https://github.com/strongloop/strong-error-handler/issues/6
|
|
60
|
-
sendJson(res, data);
|
|
54
|
+
var sendResponse = negotiateContentProducer(req, options);
|
|
55
|
+
sendResponse(res, data);
|
|
61
56
|
};
|
|
62
57
|
};
|
package/lib/logger.js
CHANGED
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
var format = require('util').format;
|
|
9
|
+
var g = require('strong-globalize')();
|
|
9
10
|
|
|
10
11
|
module.exports = function logToConsole(req, err) {
|
|
11
12
|
if (!Array.isArray(err)) {
|
|
12
|
-
|
|
13
|
+
g.error('Unhandled error for request %s %s: %s',
|
|
13
14
|
req.method, req.url, err.stack || err);
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
var errMsg = g.f('Unhandled array of errors for request %s %s\n',
|
|
19
|
+
req.method, req.url);
|
|
17
20
|
var errors = err.map(formatError).join('\n');
|
|
18
|
-
console.error(
|
|
19
|
-
req.method, req.url, errors);
|
|
21
|
+
console.error(errMsg, errors);
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
function formatError(err) {
|
package/lib/send-html.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2016. All Rights Reserved.
|
|
2
|
+
// Node module: strong-error-handler
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
var ejs = require('ejs');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var path = require('path');
|
|
10
|
+
|
|
11
|
+
var assetDir = path.resolve(__dirname, '../views');
|
|
12
|
+
var compiledTemplates = {
|
|
13
|
+
// loading default template and stylesheet
|
|
14
|
+
default: loadDefaultTemplates(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = sendHtml;
|
|
18
|
+
|
|
19
|
+
function sendHtml(res, data, options) {
|
|
20
|
+
var toRender = {options: {}, data: data};
|
|
21
|
+
// TODO: ability to call non-default template functions from options
|
|
22
|
+
var body = compiledTemplates.default(toRender);
|
|
23
|
+
sendReponse(res, body);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compile and cache the file with the `filename` key in options
|
|
28
|
+
*
|
|
29
|
+
* @param filepath (description)
|
|
30
|
+
* @returns {Function} render function with signature fn(data);
|
|
31
|
+
*/
|
|
32
|
+
function compileTemplate(filepath) {
|
|
33
|
+
var options = {cache: true, filename: filepath};
|
|
34
|
+
var fileContent = fs.readFileSync(filepath, 'utf8');
|
|
35
|
+
return ejs.compile(fileContent, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// loads and cache default error templates
|
|
39
|
+
function loadDefaultTemplates() {
|
|
40
|
+
var defaultTemplate = path.resolve(assetDir, 'default-error.ejs');
|
|
41
|
+
return compileTemplate(defaultTemplate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sendReponse(res, body) {
|
|
45
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
46
|
+
res.end(body);
|
|
47
|
+
}
|
package/lib/send-xml.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2016. All Rights Reserved.
|
|
2
|
+
// Node module: strong-error-handler
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
var js2xmlparser = require('js2xmlparser');
|
|
9
|
+
|
|
10
|
+
module.exports = function sendXml(res, data) {
|
|
11
|
+
var content = js2xmlparser.parse('error', data);
|
|
12
|
+
res.setHeader('Content-Type', 'text/xml; charset=utf-8');
|
|
13
|
+
res.end(content, 'utf-8');
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "strong-error-handler",
|
|
3
3
|
"description": "Error handler for use in development and production environments.",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.2.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/strongloop/strong-error-handler.git"
|
|
@@ -14,14 +14,27 @@
|
|
|
14
14
|
"posttest": "npm run lint"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"accepts": "^1.3.3",
|
|
17
18
|
"debug": "^2.2.0",
|
|
18
|
-
"
|
|
19
|
+
"ejs": "^2.4.2",
|
|
20
|
+
"http-status": "^0.2.2",
|
|
21
|
+
"js2xmlparser": "^2.0.2",
|
|
22
|
+
"strong-globalize": "^2.6.7"
|
|
19
23
|
},
|
|
20
24
|
"devDependencies": {
|
|
21
25
|
"chai": "^2.1.1",
|
|
22
|
-
"eslint": "^2.
|
|
23
|
-
"eslint-config-loopback": "^
|
|
26
|
+
"eslint": "^2.13.1",
|
|
27
|
+
"eslint-config-loopback": "^4.0.0",
|
|
28
|
+
"express": "^4.13.4",
|
|
24
29
|
"mocha": "^2.1.0",
|
|
25
30
|
"supertest": "^1.1.0"
|
|
31
|
+
},
|
|
32
|
+
"browser": {
|
|
33
|
+
"strong-error-handler": false
|
|
34
|
+
},
|
|
35
|
+
"ci": {
|
|
36
|
+
"downstreamIgnoreList": [
|
|
37
|
+
"dashboard-controller"
|
|
38
|
+
]
|
|
26
39
|
}
|
|
27
40
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<meta charset='utf-8'>
|
|
4
|
+
<title><%- data.name || data.message %></title>
|
|
5
|
+
<style><%- include style.css %></style>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<div id="wrapper">
|
|
9
|
+
<h1><%- data.name %></h1>
|
|
10
|
+
<h2><em><%- data.statusCode %></em> <%- data.message %></h2>
|
|
11
|
+
<%
|
|
12
|
+
// display all the non-standard properties
|
|
13
|
+
var standardProps = ['name', 'statusCode', 'message', 'stack'];
|
|
14
|
+
for (var prop in data) {
|
|
15
|
+
if (standardProps.indexOf(prop) == -1 && data[prop]) { %>
|
|
16
|
+
<div><b><%- prop %></b>: <%- data[prop] %></div>
|
|
17
|
+
<% }
|
|
18
|
+
}
|
|
19
|
+
if (data.stack) { %>
|
|
20
|
+
<pre id="stacktrace"><%- data.stack %></pre>
|
|
21
|
+
<% }
|
|
22
|
+
%>
|
|
23
|
+
</div>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
package/views/style.css
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
outline: 0;
|
|
5
|
+
}
|
|
6
|
+
body {
|
|
7
|
+
padding: 80px 100px;
|
|
8
|
+
font: 13px "Helvetica Neue", "Lucida Grande", "Arial";
|
|
9
|
+
background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
|
|
10
|
+
background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
|
|
11
|
+
background-repeat: no-repeat;
|
|
12
|
+
color: #555;
|
|
13
|
+
-webkit-font-smoothing: antialiased;
|
|
14
|
+
}
|
|
15
|
+
h1, h2 {
|
|
16
|
+
font-size: 22px;
|
|
17
|
+
color: #343434;
|
|
18
|
+
}
|
|
19
|
+
h1 em, h2 em {
|
|
20
|
+
padding: 0 5px;
|
|
21
|
+
font-weight: normal;
|
|
22
|
+
}
|
|
23
|
+
h1 {
|
|
24
|
+
font-size: 60px;
|
|
25
|
+
}
|
|
26
|
+
h2 {
|
|
27
|
+
margin: 10px 0;
|
|
28
|
+
}
|
|
29
|
+
ul li {
|
|
30
|
+
list-style: none;
|
|
31
|
+
}
|