├── .eslintrc ├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .zuul.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | rules: 2 | indent: ['error'] 3 | no-trailing-spaces: ['error'] 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | name: Node.JS ${{ matrix.node-version }} 13 | strategy: 14 | matrix: 15 | node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: Install 22 | run: yarn install --ignore-engines # Ignore engines to avoid eslint errors on old versions 23 | - name: Test 24 | run: yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | *~ -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-bdd 2 | browsers: 3 | - name: chrome 4 | version: latest -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.0 2 | 3 | * Added support for node v7 4 | * Dropped support for node v0.8 and v0.6 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt Lavin 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nested stacktraces for Node.js! 2 | =============================== 3 | 4 | [![Build Status](https://github.com/mdlavin/nested-error-stacks/actions/workflows/test.yaml/badge.svg)](https://github.com/mdlavin/nested-error-stacks/actions?query=branch%3Amaster) 5 | [![NPM version](https://badge.fury.io/js/nested-error-stacks.svg)](http://badge.fury.io/js/nested-error-stacks) 6 | 7 | With this module, you can wrap a caught exception with extra context 8 | for better debugging. For example, a network error's stack would normally look 9 | like this: 10 | 11 | Error: connect ECONNREFUSED 12 | at errnoException (net.js:904:11) 13 | at Object.afterConnect [as oncomplete] (net.js:895:19) 14 | 15 | Using this module, you can wrap the Error with more context to get a stack 16 | that looks like this: 17 | 18 | NestedError: Failed to communicate with localhost:8080 19 | at Socket. (/Users/mattlavin/Projects/nested-stacks/demo.js:6:18) 20 | at Socket.EventEmitter.emit (events.js:95:17) 21 | at net.js:440:14 22 | at process._tickCallback (node.js:415:13) 23 | Caused By: Error: connect ECONNREFUSED 24 | at errnoException (net.js:904:11) 25 | at Object.afterConnect [as oncomplete] (net.js:895:19) 26 | 27 | How to wrap errors 28 | ------------------ 29 | 30 | Here is an example program that uses this module to add more context to errors: 31 | 32 | ```js 33 | var NestedError = require('nested-error-stacks'); 34 | var net = require('net'); 35 | 36 | var client = net.connect({port: 8080}); 37 | client.on('error', function (err) { 38 | var newErr = new NestedError("Failed to communicate with localhost:8080", err); 39 | console.log(newErr.stack); 40 | }); 41 | ``` 42 | 43 | How to inherit 44 | -------------- 45 | 46 | It is recommended to use explicit names for Error classes. You can do it 47 | like this: 48 | 49 | ```js 50 | var util = require('util'); 51 | var NestedError = require('nested-error-stacks'); 52 | 53 | function MyError(message, nested) { 54 | NestedError.call(this, message, nested); 55 | } 56 | 57 | util.inherits(MyError, NestedError); 58 | MyError.prototype.name = 'MyError'; 59 | ``` 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | 3 | var NestedError = function (message, nested) { 4 | this.nested = nested; 5 | 6 | if (message instanceof Error) { 7 | nested = message; 8 | } else if (typeof message !== 'undefined') { 9 | Object.defineProperty(this, 'message', { 10 | value: message, 11 | writable: true, 12 | enumerable: false, 13 | configurable: true 14 | }); 15 | } 16 | 17 | Error.captureStackTrace(this, this.constructor); 18 | var oldStackDescriptor = Object.getOwnPropertyDescriptor(this, 'stack'); 19 | var stackDescriptor = buildStackDescriptor(oldStackDescriptor, nested); 20 | Object.defineProperty(this, 'stack', stackDescriptor); 21 | }; 22 | 23 | function buildStackDescriptor(oldStackDescriptor, nested) { 24 | if (oldStackDescriptor.get) { 25 | return { 26 | get: function () { 27 | var stack = oldStackDescriptor.get.call(this); 28 | return buildCombinedStacks(stack, this.nested); 29 | } 30 | }; 31 | } else { 32 | var stack = oldStackDescriptor.value; 33 | return { 34 | value: buildCombinedStacks(stack, nested) 35 | }; 36 | } 37 | } 38 | 39 | function buildCombinedStacks(stack, nested) { 40 | if (nested) { 41 | stack += '\nCaused By: ' + nested.stack; 42 | } 43 | return stack; 44 | } 45 | 46 | inherits(NestedError, Error); 47 | NestedError.prototype.name = 'NestedError'; 48 | 49 | 50 | module.exports = NestedError; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nested-error-stacks", 3 | "version": "2.1.1", 4 | "description": "An Error subclass that will chain nested Errors and dump nested stacktraces", 5 | "bugs": { 6 | "url": "https://github.com/mdlavin/nested-error-stacks/issues" 7 | }, 8 | "keywords": [ 9 | "error", 10 | "nested", 11 | "stack" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/mdlavin/nested-error-stacks.git" 16 | }, 17 | "main": "index.js", 18 | "files": [ 19 | "index.js", 20 | "LICENSE" 21 | ], 22 | "scripts": { 23 | "test": "if node --version | grep -vE '^v(0|3|4|5|6|7)\\.' > /dev/null 2>&1; then eslint . || exit -1; fi; if [ \"$RUN_ZUUL\" != \"true\" ]; then node_modules/.bin/mocha; else npm install zuul && node_modules/.bin/zuul -- test.js; fi" 24 | }, 25 | "author": "Matt Lavin ", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "chai": "^3.5.0", 29 | "eslint": "^6.6.0", 30 | "mocha": "^3.1.2", 31 | "uuid": "^2.0.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var NestedError = require('./index.js'); 2 | var expect = require('chai').expect; 3 | var uuid = require('uuid'); 4 | var inherits = require('util').inherits; 5 | 6 | describe('NestedErrors', function () { 7 | 8 | it('are instances of Error', function () { 9 | var error = new NestedError(); 10 | expect(error).to.be.an.instanceOf(Error); 11 | expect(error).to.be.an.instanceOf(NestedError); 12 | }); 13 | 14 | it('include message in stacktrace', function () { 15 | var message = uuid.v1(); 16 | var nested = new NestedError(message); 17 | 18 | expect(nested.stack).to.contain(message); 19 | }); 20 | 21 | it('includes child stack in stacktrace', function () { 22 | var childMessage = uuid.v1(); 23 | var child = new Error(childMessage); 24 | 25 | var message = uuid.v1(); 26 | var nested = new NestedError(message, child); 27 | 28 | expect(nested.stack).to.contain('Caused By: ' + child.stack); 29 | }); 30 | 31 | it('allows multiple nested errors', function () { 32 | var childMessage = uuid.v1(); 33 | var child = new Error(childMessage); 34 | 35 | var child2Message = uuid.v1(); 36 | var child2 = new NestedError(child2Message, child); 37 | 38 | var message = uuid.v1(); 39 | var nested = new NestedError(message, child2); 40 | 41 | expect(nested.stack).to.contain('Caused By: ' + child2.stack); 42 | }); 43 | 44 | it('includes Error subclass names in stacks', function () { 45 | var SubclassError = function (message) { 46 | NestedError.call(this, message); 47 | }; 48 | inherits(SubclassError, NestedError); 49 | SubclassError.prototype.name = 'SubclassError'; 50 | 51 | var message = uuid.v1(); 52 | var error = new SubclassError(message); 53 | expect(error.stack).to.contain('SubclassError'); 54 | expect(error.stack).to.contain(message); 55 | }); 56 | 57 | it('includes Error subclass with nesting', function () { 58 | var SubclassError = function (message, nested) { 59 | NestedError.call(this, message, nested); 60 | }; 61 | inherits(SubclassError, NestedError); 62 | SubclassError.prototype.name = 'SubclassError'; 63 | 64 | var childMessage = uuid.v1(); 65 | var child = new Error(childMessage); 66 | 67 | var message = uuid.v1(); 68 | var error = new SubclassError(message, child); 69 | 70 | expect(error.stack).to.contain('Caused By: ' + child.stack); 71 | }); 72 | 73 | it('messages are ECMAScript6 compatible', function () { 74 | var nested = new NestedError(uuid.v1()); 75 | var messageDescriptor = Object.getOwnPropertyDescriptor(nested, 'message'); 76 | 77 | expect(messageDescriptor.writable).to.be.equal(true); 78 | expect(messageDescriptor.enumerable).to.be.equal(false); 79 | expect(messageDescriptor.configurable).to.be.equal(true); 80 | }); 81 | 82 | it('without messages are ECMAScript6 compatible', function () { 83 | var nested = new NestedError(); 84 | var messageDescriptor = Object.getOwnPropertyDescriptor(nested, 'message'); 85 | 86 | expect(messageDescriptor).to.be.undefined; 87 | }); 88 | 89 | it('has stack property attributes that match other errors', function () { 90 | var normal = new Error(); 91 | var nested = new Error('test', normal); 92 | 93 | var normalStackDescriptor = Object.getOwnPropertyDescriptor(normal, 'stack'); 94 | var nestedStackDescriptor = Object.getOwnPropertyDescriptor(nested, 'stack'); 95 | 96 | expect(nestedStackDescriptor.enumerable, 'enumerable').to.equal(normalStackDescriptor.enumerable); 97 | expect(nestedStackDescriptor.configurable, 'configurable').to.equal(normalStackDescriptor.configurable); 98 | expect(nestedStackDescriptor.writable, 'writable').to.equal(normalStackDescriptor.writable); 99 | }); 100 | 101 | it('can accept only error parameter', function () { 102 | var error = new Error(); 103 | var nestedError = new NestedError(error); 104 | expect(nestedError).to.be.an.instanceOf(NestedError); 105 | }); 106 | }); 107 | --------------------------------------------------------------------------------