Martin
Martin

Reputation: 641

Understanding Error.captureStackTrace and stack trace persistance?

I a while ago i used to use Verror from joyent to not lose the stacktrace when rethrowing, I have just done a test with node v12 without Verror and stack trace seems to be persisted without the use of error.

I then was looking at the use of Error.captureStackTrace, it states to use this in your errors so that the Error is not added to the stack.

I don't what i am doing wrong but with or without Error.captureStackTrace - the stack trace is the same..

I would like to know the current status of the use of the captureStackTrace as i see no difference :-) and also the use of VError that doesn't seem to be needed anymore.

In the documentation it states that the .stack isn't available without calling the captureStackTrace but I see it available each time, with or without the captureStackTrace

here is an example of 2 x errors

class MyErrorOne extends Error {
  constructor(message) {
    super(message);

    Error.captureStackTrace(this, this.constructor);
  }
}

class MyErrorTwo extends Error {
  constructor(message) {
    super(message);

    Error.captureStackTrace(this, this.constructor);
  }
}

I tried them by commenting out the captureStackTrace in EACH error and the stacktrace is the same.

Can anyone help ?

Here is my code to test calling the errors

const DoOne = () => {
  try {
    console.log("executing do one");
    DoTwo();
  } catch (error) {
    console.log("error in DoOne", error);
    console.log("here is the stack ", error.stack);
    throw new MyErrorOne("threw error from doone in myerrorone");
    // throw error;
  }
};

const DoTwo = () => {
  try {
    console.log("executing do two");
    throw new MyErrorTwo("threw error from dotwo in myerrortwo");
  } catch (error) {
    console.log("error in DoTwo", error);
    throw error;
  }
};

DoOne();

Upvotes: 31

Views: 70413

Answers (4)

Yilmaz
Yilmaz

Reputation: 49361

captureStackTrace returns a string that represents the location of that particular error in the call. It gives us a stack that helps us to find the location of that error in the code at which new Error() was Called. this will help us to find the exact error in our code.

I am not sure which documentation you mention, but if you read V8 stack trace docs

Error.captureStackTrace(error, constructorOpt)

adds a stack property to the given error object that yields the stack trace at the time captureStackTrace was called. Stack traces collected through Error.captureStackTrace are immediately collected, formatted, and attached to the given error object.

The optional constructorOpt parameter allows you to pass in a function value. When collecting the stack trace all frames above the topmost call to this function, including that call, are left out of the stack trace. This can be useful to hide implementation details that won’t be useful to the user. The usual way of defining a custom error that captures a stack trace would be:

function MyError() {
      // if you are calling this in constructor, instead of MyError, "this.constructor"
      Error.captureStackTrace(this, MyError);
      // Any other initialization goes here.
    }

Passing in MyError as a second argument means that the constructor call to MyError won’t show up in the stack trace.

Upvotes: 1

user3342816
user3342816

Reputation: 1263

When calling Error.captureStackTrace(obj[, fun]) a formatted call stack is attached to obj including the line/frame where captureStackTrace was called.

When specifying fun all frames above and including fun are removed.

Examples:

Note: In sample outputs I have removed anything below call to fun1 to minimize clutter.

const fun1 = () => { fun2(); };
const fun2 = () => { fun3(); };
const fun3 = () => { log_stack(); };
function log_stack() {
    let err = {};
    Error.captureStackTrace(err);
    console.log(err.stack);
}
fun1();

This yields:

Error
    at log_stack (/path/to/example.js:6:8)
    at fun3 (/path/to/example.js:3:22)
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

Now, if we add log_stack as the function option to captureStackTrace:

Error.captureStackTrace(err, log_stack);

It yields:

Error
    at fun3 (/path/to/example.js:3:22)
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

The log_stack frame is not there anymore.

Adding fun3 to the captureStackTrace:

Error.captureStackTrace(err, fun3);

It yields:

Error
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

Etc.

In your case, if you change:

Error.captureStackTrace(this, this.constructor);

to:

Error.captureStackTrace(this);

You will see that you get an additional line with the new MyError...:

error in DoTwo MyErrorTwo: threw error from dotwo in myerrortwo
    at new MyErrorTwo (/path/to/testerrorclass.js:10:9)  <<== Not removed anymore.

Upvotes: 40

Blaskovicz
Blaskovicz

Reputation: 6160

This is my implementation that I found a while back, the key is to restore the prototype chain (typescript):

import {INTERNAL_SERVER_ERROR} from 'http-status-codes';

export class RouteError extends Error {
  public statusCode: number;
  /**
   * Create a new RouteError object to be handled in 'express-async-handler' wrapped middleware.
   * @param message {string} [message=An error occurred.] - The json message key to return to the user.
   * @param statusCode {number} [statusCode=INTERNAL_SERVER_ERROR] - response code to send.
   */
  constructor(message: string = 'An error occurred.', statusCode: number = INTERNAL_SERVER_ERROR) {
    super(message); // 'Error' breaks prototype chain here
    Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    this.statusCode = statusCode;
  }
}

Upvotes: 0

yuvke
yuvke

Reputation: 99

I'm not sure, but it seems that Error.captureStackTrace is intended to be used when constructing Error object without extending the Error class (see also the example provided in the Node docs.

I'm guessing that when you're extending the Error class, capturing the stack trace is already done by the Error constructor.

I've tested your code and remove the extending of Error and indeed if you don't call captureStackTrace you will not get a stack trace on your error objects.

I'm not sure what would be the use case of throwing Errors that are not extending the Error class.

Upvotes: 9

Related Questions