Error-tracking software doesn't catch hapi application errors if an incoming request gets prematurely closed
- Dominant language
- JavaScript
- Stars
- 14.8k
- Forks
- 1.4k
- Avg merge
- 22d 3h
- Merged PRs (30d)
- 1
Description
### Runtime
Node.js
### Runtime version
v25.7.0
### Module version
v21.4.7
### Last module version without issue
_No response_
### Used with
Sentry, Rollbar, DataDog
### Any other relevant information
Sentry, Rollbar, and DataDog support tracking errors in hapi applications, relying on either hapi's `request` `error` event or on `onPreResponse` lifecycle method. See:
- https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/integrations/tracing/hapi/index.ts#L67
- https://docs.rollbar.com/docs/nodejs#using-hapi
- https://github.com/DataDog/dd-trace-js/blob/master/packages/datadog-instrumentations/src/hapi.js#L33
### What are you trying to achieve or the steps to reproduce?
Here is example code that reproduces an issue where an error thrown in a handler bypasses both the `request` `error` event and `onPreResponse` lifecycle method calls.
```js
import Hapi from "@hapi/hapi";
import { setTimeout as sleep } from "node:timers/promises"
const server = Hapi.server({
port: 3000,
host: "localhost"
});
server.events.on({ name: "request", channels: ["error"] }, (request, event, tags) => {
console.log("[server] on request error"); // NOT called
})
server.ext("onPreResponse", async (request, h) => {
console.log("[server] onPreResponse"); // NOT called
return request;
});
server.route({
method: "GET",
path: "/sleep/{seconds}/then-throw-error",
handler: async (request, h) => {
const { seconds } = request.params;
for (let i = 0; i < seconds; i++) {
console.log("[handler] Handling a request... (sleep %s)", i + 1);
await sleep(1_000);
}
console.log("[handler] Throwing error");
throw new Error("Boom!");
}
});
await server.start();
console.log("[server] Running on %s", server.info.uri);
try {
console.log("[fetch] Making a request");
await fetch("http://localhost:3000/sleep/5/then-throw-error", { signal: AbortSignal.timeout(1_000) });
} catch (error) {
if (error.name === "TimeoutError") {
console.log("[fetch] Stopped waiting for the response");
} else {
console.log(error);
}
}
```
### What was the result you got?
Here is the output produced by the example code:
```plain
[server] Running on http://localhost:3000
[fetch] Making a request
[handler] Handling a request... (sleep 1)
[fetch] Stopped waiting for the response
[handler] Handling a request... (sleep 2)
[handler] Handling a request... (sleep 3)
[handler] Handling a request... (sleep 4)
[handler] Handling a request... (sleep 5)
[handler] Throwing error
```
Neither `server.events.on({ name: "request", channels: ["error"] }, fn)` nor `server.ext("onPreResponse", fn)` callback functions are called if the incoming request gets prematurely closed. However, if we increase the request timeout, e.g., `AbortSignal.timeout(10_000)`, both callback functions will get called.
### What result did you expect?
I'm not sure if the shown hapi behavior is a bug or if it works as intended, especially because the `onPreResponse ` documentation [mentions](https://hapi.dev/api/?v=21.4.3#request-lifecycle) that it is “always called, _unless the request is aborted_.” Nevertheless, it's evident that there is existing error-tracking software that seems to be expecting to capture _all_ errors using the approach. The fact that some errors are not getting captured is an issue. I would like to start a discussion to figure out a way forward on how to improve error tracking around aborted requests.
Contributor guide
Assessment
This issue has not been assessed yet.