在NodeJS中重新抛出异常而不会丢失堆栈跟踪

ala*_*yor 34 javascript exception-handling node.js expect.js

如何在nodejs/javascript中重新抛出错误或异常并包含自定义消息.

我有以下代码

var json = JSON.parse(result);
Run Code Online (Sandbox Code Playgroud)

result如果发生任何解析错误,我想在异常消息中包含内容.像这样的东西.

1.  try {
2.    var json = JSON.parse(result);
3.    expect(json.messages.length).to.be(1);
4.  } catch(ex) {
5.    throw new Error(ex.message + ". " + "JSON response: " + result);
6.  }
Run Code Online (Sandbox Code Playgroud)

这里的问题是我丢失了堆栈跟踪.

有没有办法做到这一点java

throw new Error("JSON response: " + result, ex);
Run Code Online (Sandbox Code Playgroud)

Mat*_*att 40

我不知道像Java这样的原生方法,我还没有找到一个包装错误的优雅解决方案.

创建a的问题new Error是你可以丢失附加到Error抛出的原始元数据的元数据,堆栈和类型通常是留下的东西.

对现有错误进行修改的速度更快,但仍然可以修改错误中的数据.在其他地方创建的错误中进行调查也是错误的.

创建一个新的错误和新堆栈

可以修改.stacknew 的属性,Error以便在抛出之前说出您喜欢的内容.stack完全替换errors 属性可能会让调试变得非常混乱.

当原始抛出错误和错误处理程序位于不同的位置或文件中时,您可能能够跟踪原始错误的源,但不能跟踪实际捕获错误的处理程序.为了避免这种情况,最好保留对这两个错误的引用stack.如果存储了其他元数据,则访问完整的原始错误也很有用.

这是一个捕获错误的示例,将其包装在新错误中但添加原始文件stack并存储error:

try {
  throw new Error('First one')
} catch (error) {
  let e = new Error(`Rethrowing the "${error.message}" error`)
  e.original = error
  e.stack = e.stack.split('\n').slice(0,2).join('\n') + '\n' +
            error.stack
  throw e
}
Run Code Online (Sandbox Code Playgroud)

哪个投掷:

/so/42754270/test.js:9
    throw e
    ^

Error: Rethrowing the "First one" error
    at test (/so/42754270/test.js:5:13)
Error: First one
    at test (/so/42754270/test.js:3:11)
    at Object.<anonymous> (/so/42754270/test.js:13:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
Run Code Online (Sandbox Code Playgroud)

所以我们创建了一个新的通用Error.不幸的是,原始错误的类型从输出中隐藏,但是error已经附加,.original因此仍然可以访问它.stack除了重要的生成线以及stack附加的原始错误之外,新的基本上已被移除.

尝试解析堆栈跟踪的任何工具可能无法使用此更改或最佳情况,它们会检测到两个错误.

ES2015错误类

将其变成可重复使用的ES2015 + Error类......

// Standard error extender from @deployable/errors

class ExtendedError extends Error {
  constructor(message){
    super(message)
    this.name = this.constructor.name
    this.message = message
    if (typeof Error.captureStackTrace === 'function'){
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class RethrownError extends ExtendedError {
  constructor(message, error){
    super(message)
    if (!error) throw new Error('RethrownError requires a message and error')
    this.original = error
    this.new_stack = this.stack
    let message_lines =  (this.message.match(/\n/g)||[]).length + 1
    this.stack = this.stack.split('\n').slice(0, message_lines+1).join('\n') + '\n' +
                 error.stack
  }
}

throw new RethrownError(`Oh no a "${error.message}" error`, error)
Run Code Online (Sandbox Code Playgroud)

结果是

/so/42754270/test2.js:31
    throw new RethrownError(`Oh no a "${error.message}"" error`, error)
    ^

RethrownError: Oh no a "First one" error
    at test (/so/42754270/test2.js:31:11)
Error: First one
    at test (/so/42754270/test2.js:29:11)
    at Object.<anonymous> (/so/42754270/test2.js:35:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
Run Code Online (Sandbox Code Playgroud)

然后你知道,每当你看到RethrownError原始错误仍然可用时.original.

这种方法并不完美,但这意味着我可以将基础模块中的已知错误输入到更容易处理的泛型类型中,通常使用bluebirds 过滤捕获.catch(TypeError, handler)

修改堆栈的相同错误

有时您需要保持原始错误.

在这种情况下,您只需将新信息附加/插入现有堆栈即可.

file = '/home/jim/plumbers'
try {
   JSON.parse('k')
} catch (e) {
   let message = `JSON parse error in ${file}`
   let stack = new Error(message).stack
   e.stack = e.stack + '\nFrom previous ' + stack.split('\n').slice(0,2).join('\n') + '\n'
   throw e
}
Run Code Online (Sandbox Code Playgroud)

哪个回报

/so/42754270/throw_error_replace_stack.js:13
       throw e
       ^

SyntaxError: Unexpected token k in JSON at position 0
    at Object.parse (native)
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:8:13)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
From previous Error: JSON parse error in "/home/jim/plumbers"
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:11:20)
Run Code Online (Sandbox Code Playgroud)

另请注意,堆栈处理很简单,并假设错误消息是单行.如果遇到多行错误消息,则可能需要查找\n at以终止消息.


Sha*_*ing 9

如果您只想更改消息,则可以更改消息:

try {
  throw new Error("Original Error");
} catch(err) {
  err.message = "Here is some context -- " + err.message;
  throw err;
}
Run Code Online (Sandbox Code Playgroud)

  • 这会导致“TypeError: Cannot set property message of which has only a getter”。可能有办法做到这一点,但我还没有深入研究。您关于 DOMException 不在 Node 中的观点很受欢迎。我将删除我的反对票。Node 中可能存在其他类型的错误,但 Node 的“message”属性同样是只读的。 (2认同)

Sco*_*son 8

JavaScript 引入了创建新错误并找出“原因”的功能,从而保留原始堆栈跟踪(如此处所述。它看起来像这样:

try {
  someDangerousLogic();
} catch (originalError) {
  throw new Error(
    'Some additional, useful information',
    { cause: originalError }
  );
}
Run Code Online (Sandbox Code Playgroud)

完整示例:

function someDangerousLogic() {
  throw new Error('Whoops!');
}

function main() {
  try {
    someDangerousLogic();
  } catch (originalError) {
    throw new Error(
      'Some additional, useful information',
      { cause: originalError }
    );
  }
}

main();
Run Code Online (Sandbox Code Playgroud)

不幸的是,当错误未被捕获时,并非所有浏览器都会显示错误原因。如果我在 Chrome v110 中运行上面的“完整示例”,然后查看开发工具中的错误,我只会找到新的堆栈跟踪(“原因”仍然可以通过属性访问.cause)。另一方面,当错误未被捕获时,Node 会显示错误和原因:

Error: Some additional, useful information
    at main (/home/me/temp.js:9:11)
    at Object.<anonymous> (/home/me/temp.js:16:1)
    ... 5 lines matching cause stack trace ...
    at node:internal/main/run_main_module:17:47 {
  [cause]: Error: Whoops!
      at someDangerousLogic (/home/me/temp.js:2:9)
      at main (/home/me/temp.js:7:5)
      at Object.<anonymous> (/home/me/temp.js:16:1)
      at Module._compile (node:internal/modules/cjs/loader:1112:14)
      at Module._extensions..js (node:internal/modules/cjs/loader:1166:10)
      at Module.load (node:internal/modules/cjs/loader:988:32)
      at Module._load (node:internal/modules/cjs/loader:834:12)
      at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
      at node:internal/main/run_main_module:17:47
}
Run Code Online (Sandbox Code Playgroud)