hapi icon indicating copy to clipboard operation
hapi copied to clipboard

Asserts (and probably other Bounce.isSystem) loose stack trace

Open legraphista opened this issue 1 year ago • 1 comments

Runtime

node.js

Runtime version

22.9.0

Module version

@hapi/[email protected]

Last module version without issue

unknown

Used with

standalone

Any other relevant information

When throwing an unhandled exception, the stack is available in the boom object in onPreResponse, but not for "system" exceptions like AssertionError


Culprit:

https://github.com/hapijs/hapi/blob/22377ee7329b553f9ef72ff869940dce05523920/lib/toolkit.js#L64 The error is passed as message instead of data

https://github.com/hapijs/boom/blob/01a4996dc6e62949aa7a98ca75b2d0dcd8a4a0a8/lib/index.js#L74 Boom catches it as error, but Hoek.clone loses the stack trace ❗

> messageOrError.stack
'AssertionError [ERR_ASSERTION]: Hello World
    at handler (/home/stefan/[redacted]/src/index.ts:26:9)
    at exports.Manager.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/toolkit.js:57:29)
    at internals.handler (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:46:48)
    at exports.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:31:36)
    at Request._lifecycle (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:370:68)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Request._execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:280:9)'
> Hoek.clone(messageOrError).stack
undefined

https://github.com/hapijs/hoek/blob/0f0a14eae94d28b4826e3bb4b2071b4eccbd398f/lib/clone.js#L91-L94 when key === 'stack', the descriptor is retrieved and applied to newObj, but descriptor.get() is undefined

> Object.getOwnPropertyDescriptor(new Error('hello'), 'stack').get()
undefined

What are you trying to achieve or the steps to reproduce?

export const hapiServer = new Server({
    port: 3000,
    address: "0.0.0.0"
});

hapiServer.ext('onPreResponse', (request, h) => {
    const response = request.response;
    if (response instanceof Error) {
        console.error(request.info.id, response);
    }
    return h.continue;
});

hapiServer.route({
    method: 'GET',
    path: '/assert',
    handler: (request, h) => {
        assert(false, 'Hello World');
    }
});

hapiServer.route({
    method: 'GET',
    path: '/throw',
    handler: (request, h) => {
        throw new Error('Hello World');
    }
});

await hapiServer.start()

What was the result you got?

curl 127.0.0.1:3000/assert

1729274024376:something:416933:m2f13fpr:10000 [AssertionError: Hello World] {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '==',
  isBoom: true,
  isServer: true,
  data: null,
  output: [Object],
  isDeveloperError: true
}
curl localhost:3000/throw

1729274081766:something:416933:m2f13fpr:10001 Error: Hello World
    at handler (/home/stefan/[redacted]/src/index.ts:34:15)
    at exports.Manager.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/toolkit.js:57:29)
    at internals.handler (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:46:48)
    at exports.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:31:36)
    at Request._lifecycle (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:370:68)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Request._execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:280:9) {
  isBoom: true,
  isServer: true,
  data: null,
  output: {
    statusCode: 500,
    payload: {
      statusCode: 500,
      error: 'Internal Server Error',
      message: 'An internal server error occurred'
    },
    headers: {}
  }
}

What result did you expect?

curl 127.0.0.1:3000/assert

1729274228005:something:419617:m2f183s8:10000 AssertionError [ERR_ASSERTION]: Hello World
    at handler (/home/stefan/[redacted]/src/index.ts:26:9)
    at exports.Manager.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/toolkit.js:57:29)
    at internals.handler (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:46:48)
    at exports.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:31:36)
    at Request._lifecycle (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:370:68)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Request._execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:280:9) {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '==',
  isBoom: true,
  isServer: true,
  data: null,
  output: [Object]
}
curl localhost:3000/throw

1729274081766:something:416933:m2f13fpr:10001 Error: Hello World
    at handler (/home/stefan/[redacted]/src/index.ts:34:15)
    at exports.Manager.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/toolkit.js:57:29)
    at internals.handler (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:46:48)
    at exports.execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/handler.js:31:36)
    at Request._lifecycle (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:370:68)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Request._execute (/home/stefan/[redacted]/node_modules/@hapi/hapi/lib/request.js:280:9) {
  isBoom: true,
  isServer: true,
  data: null,
  output: {
    statusCode: 500,
    payload: {
      statusCode: 500,
      error: 'Internal Server Error',
      message: 'An internal server error occurred'
    },
    headers: {}
  }
}

legraphista avatar Oct 18 '24 18:10 legraphista

Thanks for the report. This is a known issue with Hoek.clone() and node v21+. I have proposed a fix in https://github.com/hapijs/hoek/pull/390, and made an issue against hapi in #4519.

Until this is fixed, I would suggest you use node 20 for running hapi.

kanongil avatar Oct 19 '24 14:10 kanongil

I just released a new hapi version (21.3.11) that fixes this.

Marsup avatar Oct 24 '24 10:10 Marsup