Skip to content

errorHandler option cannot reach the raw socket 'error' listener attached in handleUpgrade — no way to classify expected disconnect errors #371

Description

@Bre77

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions