Prerequisites
Fastify version
5.6.1
Plugin version
11.2.0
Node.js version
22.x
Operating system
Linux
Description
The errorHandler plugin option only covers errors thrown by (or rejected from) the route's wsHandler. There is a second error path it cannot reach: handleUpgrade attaches a raw listener directly to the upgraded socket that logs unconditionally at error level:
// index.js (v11.2.0, lines 126–136)
const handleUpgrade = (rawRequest, callback) => {
wss.handleUpgrade(rawRequest, rawRequest[kWs], rawRequest[kWsHead], (socket) => {
wss.emit('connection', socket, rawRequest)
socket.on('error', (error) => {
fastify.log.error(error) // <-- not routed through opts.errorHandler
})
callback(socket)
})
}
Errors emitted on the WebSocket itself — typically expected client-disconnect conditions at scale, e.g. ERR_STREAM_PREMATURE_CLOSE or ECONNRESET from a browser tab close or a flaky mobile network tearing the connection down mid-write — go through this listener, not through errorHandler. An application that wants to classify expected disconnects (log at debug) while keeping real errors at error level has no supported seam for this path:
opts.errorHandler never sees these errors (it is only invoked around the wsHandler call at index.js lines 199–205);
- the listener is attached before
callback(socket) invokes the route's wsHandler, so the only in-app workarounds are removing the plugin's listener from inside the handler (socket.removeAllListeners('error') + re-attaching a classifying one, which is brittle against plugin internals) or intercepting at the logger (a pino hooks.logMethod that downgrades specific codes — what we ended up shipping).
Expected Behavior
Errors emitted on the raw socket should be classifiable by the application, for example by routing this listener through the same errorHandler option:
socket.on('error', (error) => {
errorHandler.call(this, error, socket, request, reply)
})
(possibly with a flag or separate hook so implementations can distinguish "wsHandler rejected" from "socket errored", since errorHandler implementations commonly call socket.terminate(), which may be undesirable for a socket that already errored/closed on its own).
If maintainers agree with the direction, happy to send a PR.
Prerequisites
Fastify version
5.6.1
Plugin version
11.2.0
Node.js version
22.x
Operating system
Linux
Description
The
errorHandlerplugin option only covers errors thrown by (or rejected from) the route'swsHandler. There is a second error path it cannot reach:handleUpgradeattaches a raw listener directly to the upgraded socket that logs unconditionally at error level:Errors emitted on the WebSocket itself — typically expected client-disconnect conditions at scale, e.g.
ERR_STREAM_PREMATURE_CLOSEorECONNRESETfrom a browser tab close or a flaky mobile network tearing the connection down mid-write — go through this listener, not througherrorHandler. An application that wants to classify expected disconnects (log at debug) while keeping real errors at error level has no supported seam for this path:opts.errorHandlernever sees these errors (it is only invoked around thewsHandlercall at index.js lines 199–205);callback(socket)invokes the route'swsHandler, so the only in-app workarounds are removing the plugin's listener from inside the handler (socket.removeAllListeners('error')+ re-attaching a classifying one, which is brittle against plugin internals) or intercepting at the logger (a pinohooks.logMethodthat downgrades specific codes — what we ended up shipping).Expected Behavior
Errors emitted on the raw socket should be classifiable by the application, for example by routing this listener through the same
errorHandleroption:(possibly with a flag or separate hook so implementations can distinguish "wsHandler rejected" from "socket errored", since
errorHandlerimplementations commonly callsocket.terminate(), which may be undesirable for a socket that already errored/closed on its own).If maintainers agree with the direction, happy to send a PR.